Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / quota / ActorsParent.cpp
blobdc6b49055f46fe622206abc5d30663749b77f9f6
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 "GroupInfo.h"
15 #include "GroupInfoPair.h"
16 #include "NormalOriginOperationBase.h"
17 #include "OriginOperationBase.h"
18 #include "OriginOperations.h"
19 #include "OriginParser.h"
20 #include "OriginScope.h"
21 #include "OriginInfo.h"
22 #include "QuotaCommon.h"
23 #include "QuotaManager.h"
24 #include "ResolvableNormalOriginOp.h"
25 #include "SanitizationUtils.h"
26 #include "ScopedLogExtraInfo.h"
27 #include "UsageInfo.h"
29 // Global includes
30 #include <cinttypes>
31 #include <cstdlib>
32 #include <cstring>
33 #include <algorithm>
34 #include <cstdint>
35 #include <functional>
36 #include <new>
37 #include <numeric>
38 #include <tuple>
39 #include <type_traits>
40 #include <utility>
41 #include "DirectoryLockImpl.h"
42 #include "ErrorList.h"
43 #include "MainThreadUtils.h"
44 #include "mozIStorageAsyncConnection.h"
45 #include "mozIStorageConnection.h"
46 #include "mozIStorageService.h"
47 #include "mozIStorageStatement.h"
48 #include "mozStorageCID.h"
49 #include "mozStorageHelper.h"
50 #include "mozilla/AlreadyAddRefed.h"
51 #include "mozilla/AppShutdown.h"
52 #include "mozilla/Assertions.h"
53 #include "mozilla/Atomics.h"
54 #include "mozilla/Attributes.h"
55 #include "mozilla/AutoRestore.h"
56 #include "mozilla/BasePrincipal.h"
57 #include "mozilla/CheckedInt.h"
58 #include "mozilla/CondVar.h"
59 #include "mozilla/InitializedOnce.h"
60 #include "mozilla/Logging.h"
61 #include "mozilla/MacroForEach.h"
62 #include "mozilla/Maybe.h"
63 #include "mozilla/Mutex.h"
64 #include "mozilla/NotNull.h"
65 #include "mozilla/OriginAttributes.h"
66 #include "mozilla/Preferences.h"
67 #include "mozilla/RefPtr.h"
68 #include "mozilla/Result.h"
69 #include "mozilla/ResultExtensions.h"
70 #include "mozilla/ScopeExit.h"
71 #include "mozilla/Services.h"
72 #include "mozilla/SpinEventLoopUntil.h"
73 #include "mozilla/StaticPrefs_dom.h"
74 #include "mozilla/StaticPtr.h"
75 #include "mozilla/SystemPrincipal.h"
76 #include "mozilla/Telemetry.h"
77 #include "mozilla/TelemetryHistogramEnums.h"
78 #include "mozilla/TextUtils.h"
79 #include "mozilla/TimeStamp.h"
80 #include "mozilla/UniquePtr.h"
81 #include "mozilla/Unused.h"
82 #include "mozilla/Variant.h"
83 #include "mozilla/dom/FileSystemQuotaClientFactory.h"
84 #include "mozilla/dom/FlippedOnce.h"
85 #include "mozilla/dom/IndexedDatabaseManager.h"
86 #include "mozilla/dom/LocalStorageCommon.h"
87 #include "mozilla/dom/StorageDBUpdater.h"
88 #include "mozilla/dom/cache/QuotaClient.h"
89 #include "mozilla/dom/indexedDB/ActorsParent.h"
90 #include "mozilla/dom/ipc/IdType.h"
91 #include "mozilla/dom/localstorage/ActorsParent.h"
92 #include "mozilla/dom/quota/AssertionsImpl.h"
93 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
94 #include "mozilla/dom/quota/Client.h"
95 #include "mozilla/dom/quota/Config.h"
96 #include "mozilla/dom/quota/Constants.h"
97 #include "mozilla/dom/quota/DirectoryLock.h"
98 #include "mozilla/dom/quota/FileUtils.h"
99 #include "mozilla/dom/quota/PersistenceType.h"
100 #include "mozilla/dom/quota/QuotaManagerService.h"
101 #include "mozilla/dom/quota/ResultExtensions.h"
102 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
103 #include "mozilla/dom/quota/StreamUtils.h"
104 #include "mozilla/dom/simpledb/ActorsParent.h"
105 #include "mozilla/fallible.h"
106 #include "mozilla/ipc/BackgroundChild.h"
107 #include "mozilla/ipc/BackgroundParent.h"
108 #include "mozilla/ipc/PBackgroundChild.h"
109 #include "mozilla/ipc/PBackgroundSharedTypes.h"
110 #include "mozilla/ipc/ProtocolUtils.h"
111 #include "mozilla/net/ExtensionProtocolHandler.h"
112 #include "mozilla/StorageOriginAttributes.h"
113 #include "nsAppDirectoryServiceDefs.h"
114 #include "nsBaseHashtable.h"
115 #include "nsCOMPtr.h"
116 #include "nsCRTGlue.h"
117 #include "nsClassHashtable.h"
118 #include "nsComponentManagerUtils.h"
119 #include "nsContentUtils.h"
120 #include "nsDebug.h"
121 #include "nsDirectoryServiceUtils.h"
122 #include "nsError.h"
123 #include "nsIBinaryInputStream.h"
124 #include "nsIBinaryOutputStream.h"
125 #include "nsIConsoleService.h"
126 #include "nsIDirectoryEnumerator.h"
127 #include "nsIDUtils.h"
128 #include "nsIEventTarget.h"
129 #include "nsIFile.h"
130 #include "nsIFileStreams.h"
131 #include "nsIInputStream.h"
132 #include "nsIObjectInputStream.h"
133 #include "nsIObjectOutputStream.h"
134 #include "nsIObserver.h"
135 #include "nsIObserverService.h"
136 #include "nsIOutputStream.h"
137 #include "nsIQuotaRequests.h"
138 #include "nsIPlatformInfo.h"
139 #include "nsIPrincipal.h"
140 #include "nsIRunnable.h"
141 #include "nsIScriptObjectPrincipal.h"
142 #include "nsISupports.h"
143 #include "nsISupportsPrimitives.h"
144 #include "nsIThread.h"
145 #include "nsITimer.h"
146 #include "nsIURI.h"
147 #include "nsIWidget.h"
148 #include "nsLiteralString.h"
149 #include "nsNetUtil.h"
150 #include "nsPIDOMWindow.h"
151 #include "nsPrintfCString.h"
152 #include "nsStandardURL.h"
153 #include "nsServiceManagerUtils.h"
154 #include "nsString.h"
155 #include "nsStringFlags.h"
156 #include "nsStringFwd.h"
157 #include "nsTArray.h"
158 #include "nsTHashtable.h"
159 #include "nsTLiteralString.h"
160 #include "nsTPromiseFlatString.h"
161 #include "nsTStringRepr.h"
162 #include "nsThreadUtils.h"
163 #include "nsURLHelper.h"
164 #include "nsXPCOM.h"
165 #include "nsXPCOMCID.h"
166 #include "nsXULAppAPI.h"
167 #include "prinrval.h"
168 #include "prio.h"
169 #include "prtime.h"
171 // The amount of time, in milliseconds, that our IO thread will stay alive
172 // after the last event it processes.
173 #define DEFAULT_THREAD_TIMEOUT_MS 30000
176 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
177 * the crash timeout.
179 #define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
182 * Automatically crash the browser if shutdown of a quota client takes this
183 * long. We've chosen a value that is long enough that it is unlikely for the
184 * problem to be falsely triggered by slow system I/O. We've also chosen a
185 * value long enough so that automated tests should time out and fail if
186 * shutdown of a quota client takes too long. Also, this value is long enough
187 * so that testers can notice the timeout; we want to know about the timeouts,
188 * not hide them. On the other hand this value is less than 60 seconds which is
189 * used by nsTerminator to crash a hung main process.
191 #define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
193 static_assert(
194 SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
195 "The kill actors timeout must be shorter than the crash browser one.");
197 // profile-before-change, when we need to shut down quota manager
198 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
200 #define KB *1024ULL
201 #define MB *1024ULL KB
202 #define GB *1024ULL MB
204 namespace mozilla::dom::quota {
206 using namespace mozilla::ipc;
208 // We want profiles to be platform-independent so we always need to replace
209 // the same characters on every platform. Windows has the most extensive set
210 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
211 // FILE_PATH_SEPARATOR.
212 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
213 const char16_t QuotaManager::kReplaceChars16[] =
214 u"" CONTROL_CHARACTERS "/:*?\"<>|\\";
216 namespace {
218 /*******************************************************************************
219 * Constants
220 ******************************************************************************/
222 const uint32_t kSQLitePageSizeOverride = 512;
224 // Important version history:
225 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
226 // which caused Firefox 57 release concerns because the major schema upgrade
227 // means anyone downgrading to Firefox 56 will experience a non-operational
228 // QuotaManager and all of its clients.
229 // - Bug 1404344 got very concerned about that and so we decided to effectively
230 // rename 3.0 to 2.1, effective in Firefox 57. This works because post
231 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
232 // increases. It also works because all the upgrade did was give the DOM
233 // Cache API QuotaClient an opportunity to create its newly added .padding
234 // files during initialization/upgrade, which isn't functionally necessary as
235 // that can be done on demand.
237 // Major storage version. Bump for backwards-incompatible changes.
238 // (The next major version should be 4 to distinguish from the Bug 1290481
239 // downgrade snafu.)
240 const uint32_t kMajorStorageVersion = 2;
242 // Minor storage version. Bump for backwards-compatible changes.
243 const uint32_t kMinorStorageVersion = 3;
245 // The storage version we store in the SQLite database is a (signed) 32-bit
246 // integer. The major version is left-shifted 16 bits so the max value is
247 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
248 static_assert(kMajorStorageVersion <= 0xFFFF,
249 "Major version needs to fit in 16 bits.");
250 static_assert(kMinorStorageVersion <= 0xFFFF,
251 "Minor version needs to fit in 16 bits.");
253 const int32_t kStorageVersion =
254 int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
256 // See comments above about why these are a thing.
257 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
258 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
260 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
261 const char kIndexedDBOriginPrefix[] = "indexeddb://";
262 const char kResourceOriginPrefix[] = "resource://";
264 constexpr auto kStorageName = u"storage"_ns;
266 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
267 #define ARCHIVES_DIRECTORY_NAME u"archives"
268 #define PERSISTENT_DIRECTORY_NAME u"persistent"
269 #define PERMANENT_DIRECTORY_NAME u"permanent"
270 #define TEMPORARY_DIRECTORY_NAME u"temporary"
271 #define DEFAULT_DIRECTORY_NAME u"default"
272 #define PRIVATE_DIRECTORY_NAME u"private"
273 #define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed"
275 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
276 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
277 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
279 const int32_t kLocalStorageArchiveVersion = 4;
281 const char kProfileDoChangeTopic[] = "profile-do-change";
282 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
284 const int32_t kCacheVersion = 2;
286 /******************************************************************************
287 * SQLite functions
288 ******************************************************************************/
290 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
291 uint32_t aMinorStorageVersion) {
292 return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
295 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
296 return uint32_t(aStorageVersion >> 16);
299 nsresult CreateTables(mozIStorageConnection* aConnection) {
300 AssertIsOnIOThread();
301 MOZ_ASSERT(aConnection);
303 // Table `database`
304 QM_TRY(MOZ_TO_RESULT(
305 aConnection->ExecuteSimpleSQL("CREATE TABLE database"
306 "( cache_version INTEGER NOT NULL DEFAULT 0"
307 ");"_ns)));
309 #ifdef DEBUG
311 QM_TRY_INSPECT(const int32_t& storageVersion,
312 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
314 MOZ_ASSERT(storageVersion == 0);
316 #endif
318 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kStorageVersion)));
320 return NS_OK;
323 Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
324 AssertIsOnIOThread();
326 QM_TRY_INSPECT(const auto& stmt,
327 CreateAndExecuteSingleStepStatement<
328 SingleStepResult::ReturnNullIfNoResult>(
329 aConnection, "SELECT cache_version FROM database"_ns));
331 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
333 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
336 nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
337 int32_t aVersion) {
338 AssertIsOnIOThread();
340 QM_TRY_INSPECT(
341 const auto& stmt,
342 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
343 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
344 "UPDATE database SET cache_version = :version;"_ns));
346 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion)));
348 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
350 return NS_OK;
353 nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
354 AssertIsOnIOThread();
356 // Table `cache`
357 QM_TRY(MOZ_TO_RESULT(
358 aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
359 "( valid INTEGER NOT NULL DEFAULT 0"
360 ", build_id TEXT NOT NULL DEFAULT ''"
361 ");"_ns)));
363 // Table `repository`
364 QM_TRY(
365 MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
366 "( id INTEGER PRIMARY KEY"
367 ", name TEXT NOT NULL"
368 ");"_ns)));
370 // Table `origin`
371 QM_TRY(MOZ_TO_RESULT(
372 aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
373 "( repository_id INTEGER NOT NULL"
374 ", suffix TEXT"
375 ", group_ TEXT NOT NULL"
376 ", origin TEXT NOT NULL"
377 ", client_usages TEXT NOT NULL"
378 ", usage INTEGER NOT NULL"
379 ", last_access_time INTEGER NOT NULL"
380 ", accessed INTEGER NOT NULL"
381 ", persisted INTEGER NOT NULL"
382 ", PRIMARY KEY (repository_id, origin)"
383 ", FOREIGN KEY (repository_id) "
384 "REFERENCES repository(id) "
385 ");"_ns)));
387 #ifdef DEBUG
389 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
390 MOZ_ASSERT(cacheVersion == 0);
392 #endif
394 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion)));
396 return NS_OK;
399 OkOrErr InvalidateCache(mozIStorageConnection& aConnection) {
400 AssertIsOnIOThread();
402 static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
403 static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;
405 QM_TRY(QM_OR_ELSE_WARN(
406 // Expression.
407 ([&]() -> OkOrErr {
408 mozStorageTransaction transaction(&aConnection,
409 /*aCommitOnComplete */ false);
411 QM_TRY(QM_TO_RESULT(transaction.Start()));
412 QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery)));
413 QM_TRY(
414 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
415 QM_TRY(QM_TO_RESULT(transaction.Commit()));
417 return Ok{};
418 }()),
419 // Fallback.
420 ([&](const QMResult& rv) -> OkOrErr {
421 QM_TRY(
422 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
424 return Ok{};
425 })));
427 return Ok{};
430 nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
431 AssertIsOnIOThread();
433 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
434 "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns)));
436 QM_TRY(InvalidateCache(aConnection));
438 #ifdef DEBUG
440 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
442 MOZ_ASSERT(cacheVersion == 1);
444 #endif
446 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2)));
448 return NS_OK;
451 Result<bool, nsresult> MaybeCreateOrUpgradeCache(
452 mozIStorageConnection& aConnection) {
453 bool cacheUsable = true;
455 QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));
457 if (cacheVersion > kCacheVersion) {
458 cacheUsable = false;
459 } else if (cacheVersion != kCacheVersion) {
460 const bool newCache = !cacheVersion;
462 mozStorageTransaction transaction(
463 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
465 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
467 if (newCache) {
468 QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection)));
470 #ifdef DEBUG
472 QM_TRY_INSPECT(const int32_t& cacheVersion,
473 LoadCacheVersion(aConnection));
474 MOZ_ASSERT(cacheVersion == kCacheVersion);
476 #endif
478 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
479 nsLiteralCString("INSERT INTO cache (valid, build_id) "
480 "VALUES (0, '')"))));
482 nsCOMPtr<mozIStorageStatement> insertStmt;
484 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
485 if (insertStmt) {
486 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
487 } else {
488 QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
489 nsCOMPtr<mozIStorageStatement>,
490 aConnection, CreateStatement,
491 "INSERT INTO repository (id, name) "
492 "VALUES (:id, :name)"_ns));
495 QM_TRY(MOZ_TO_RESULT(
496 insertStmt->BindInt32ByName("id"_ns, persistenceType)));
498 QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName(
499 "name"_ns, PersistenceTypeToString(persistenceType))));
501 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()));
503 } else {
504 // This logic needs to change next time we change the cache!
505 static_assert(kCacheVersion == 2,
506 "Upgrade function needed due to cache version increase.");
508 while (cacheVersion != kCacheVersion) {
509 if (cacheVersion == 1) {
510 QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection)));
511 } else {
512 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
513 QM_WARNING(
514 "Unable to initialize cache, no upgrade path is "
515 "available!");
519 QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
522 MOZ_ASSERT(cacheVersion == kCacheVersion);
525 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
528 return cacheUsable;
531 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
532 nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
533 AssertIsOnIOThread();
535 // Check if the old database exists at all.
536 QM_TRY_INSPECT(const bool& exists,
537 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists));
539 if (!exists) {
540 // webappsstore.sqlite doesn't exist, return a null connection.
541 return nsCOMPtr<mozIStorageConnection>{};
544 QM_TRY_INSPECT(const bool& isDirectory,
545 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, IsDirectory));
547 if (isDirectory) {
548 QM_WARNING("webappsstore.sqlite is not a file!");
549 return nsCOMPtr<mozIStorageConnection>{};
552 QM_TRY_INSPECT(const auto& connection,
553 QM_OR_ELSE_WARN_IF(
554 // Expression.
555 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
556 nsCOMPtr<mozIStorageConnection>, aStorageService,
557 OpenUnsharedDatabase, &aWebAppsStoreFile,
558 mozIStorageService::CONNECTION_DEFAULT),
559 // Predicate.
560 IsDatabaseCorruptionError,
561 // Fallback. Don't throw an error, leave a corrupted
562 // webappsstore database as it is.
563 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
565 if (connection) {
566 // Don't propagate an error, leave a non-updateable webappsstore database as
567 // it is.
568 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
569 nsCOMPtr<mozIStorageConnection>{});
572 return connection;
575 Result<nsCOMPtr<nsIFile>, QMResult> GetLocalStorageArchiveFile(
576 const nsAString& aDirectoryPath) {
577 AssertIsOnIOThread();
578 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
580 QM_TRY_UNWRAP(auto lsArchiveFile,
581 QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath)));
583 QM_TRY(QM_TO_RESULT(
584 lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));
586 return lsArchiveFile;
589 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile(
590 const nsAString& aDirectoryPath) {
591 AssertIsOnIOThread();
592 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
594 QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));
596 QM_TRY(MOZ_TO_RESULT(
597 lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
599 return lsArchiveTmpFile;
602 Result<bool, nsresult> IsLocalStorageArchiveInitialized(
603 mozIStorageConnection& aConnection) {
604 AssertIsOnIOThread();
606 QM_TRY_RETURN(
607 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns));
610 nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) {
611 AssertIsOnIOThread();
612 MOZ_ASSERT(aConnection);
614 #ifdef DEBUG
616 QM_TRY_INSPECT(const auto& initialized,
617 IsLocalStorageArchiveInitialized(*aConnection));
618 MOZ_ASSERT(!initialized);
620 #endif
622 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
623 "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns)));
625 QM_TRY_INSPECT(
626 const auto& stmt,
627 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
628 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
629 "INSERT INTO database (version) VALUES (:version)"_ns));
631 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0)));
632 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
634 return NS_OK;
637 Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
638 mozIStorageConnection& aConnection) {
639 AssertIsOnIOThread();
641 QM_TRY_INSPECT(const auto& stmt,
642 CreateAndExecuteSingleStepStatement<
643 SingleStepResult::ReturnNullIfNoResult>(
644 aConnection, "SELECT version FROM database"_ns));
646 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
648 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
651 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
652 int32_t aVersion) {
653 AssertIsOnIOThread();
654 MOZ_ASSERT(aConnection);
656 nsCOMPtr<mozIStorageStatement> stmt;
657 nsresult rv = aConnection->CreateStatement(
658 "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
659 if (NS_WARN_IF(NS_FAILED(rv))) {
660 return rv;
663 rv = stmt->BindInt32ByName("version"_ns, aVersion);
664 if (NS_WARN_IF(NS_FAILED(rv))) {
665 return rv;
668 rv = stmt->Execute();
669 if (NS_WARN_IF(NS_FAILED(rv))) {
670 return rv;
673 return NS_OK;
676 template <typename FileFunc, typename DirectoryFunc>
677 Result<mozilla::Ok, nsresult> CollectEachFileEntry(
678 nsIFile& aDirectory, const FileFunc& aFileFunc,
679 const DirectoryFunc& aDirectoryFunc) {
680 AssertIsOnIOThread();
682 return CollectEachFile(
683 aDirectory,
684 [&aFileFunc, &aDirectoryFunc](
685 const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
686 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
688 switch (dirEntryKind) {
689 case nsIFileKind::ExistsAsDirectory:
690 return aDirectoryFunc(file);
692 case nsIFileKind::ExistsAsFile:
693 return aFileFunc(file);
695 case nsIFileKind::DoesNotExist:
696 // Ignore files that got removed externally while iterating.
697 break;
700 return Ok{};
704 /******************************************************************************
705 * Quota manager class declarations
706 ******************************************************************************/
708 } // namespace
710 class QuotaManager::Observer final : public nsIObserver {
711 static Observer* sInstance;
713 bool mPendingProfileChange;
714 bool mShutdownComplete;
716 public:
717 static nsresult Initialize();
719 static nsIObserver* GetInstance();
721 static void ShutdownCompleted();
723 private:
724 Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
725 MOZ_ASSERT(NS_IsMainThread());
728 ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
730 nsresult Init();
732 nsresult Shutdown();
734 NS_DECL_ISUPPORTS
735 NS_DECL_NSIOBSERVER
738 namespace {
740 /*******************************************************************************
741 * Local class declarations
742 ******************************************************************************/
744 } // namespace
746 namespace {
748 class CollectOriginsHelper final : public Runnable {
749 uint64_t mMinSizeToBeFreed;
751 Mutex& mMutex;
752 CondVar mCondVar;
754 // The members below are protected by mMutex.
755 nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
756 uint64_t mSizeToBeFreed;
757 bool mWaiting;
759 public:
760 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
762 // Blocks the current thread until origins are collected on the main thread.
763 // The returned value contains an aggregate size of those origins.
764 int64_t BlockAndReturnOriginsForEviction(
765 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);
767 private:
768 ~CollectOriginsHelper() = default;
770 NS_IMETHOD
771 Run() override;
774 /*******************************************************************************
775 * Other class declarations
776 ******************************************************************************/
778 class StoragePressureRunnable final : public Runnable {
779 const uint64_t mUsage;
781 public:
782 explicit StoragePressureRunnable(uint64_t aUsage)
783 : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
784 mUsage(aUsage) {}
786 private:
787 ~StoragePressureRunnable() = default;
789 NS_DECL_NSIRUNNABLE
792 class RecordTimeDeltaHelper final : public Runnable {
793 const Telemetry::HistogramID mHistogram;
795 // TimeStamps that are set on the IO thread.
796 LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
797 LazyInitializedOnceNotNull<const TimeStamp> mEndTime;
799 // A TimeStamp that is set on the main thread.
800 LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;
802 public:
803 explicit RecordTimeDeltaHelper(const Telemetry::HistogramID aHistogram)
804 : Runnable("dom::quota::RecordTimeDeltaHelper"), mHistogram(aHistogram) {}
806 TimeStamp Start();
808 TimeStamp End();
810 private:
811 ~RecordTimeDeltaHelper() = default;
813 NS_DECL_NSIRUNNABLE
816 /*******************************************************************************
817 * Helper classes
818 ******************************************************************************/
820 /*******************************************************************************
821 * Helper Functions
822 ******************************************************************************/
824 // Return whether the group was actually updated.
825 Result<bool, nsresult> MaybeUpdateGroupForOrigin(
826 OriginMetadata& aOriginMetadata) {
827 MOZ_ASSERT(!NS_IsMainThread());
829 bool updated = false;
831 if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
832 if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
833 aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
834 updated = true;
836 } else {
837 nsCOMPtr<nsIPrincipal> principal =
838 BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin);
839 QM_TRY(MOZ_TO_RESULT(principal));
841 QM_TRY_INSPECT(const auto& baseDomain,
842 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
843 GetBaseDomain));
845 const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
847 if (aOriginMetadata.mGroup != upToDateGroup) {
848 aOriginMetadata.mGroup = upToDateGroup;
849 updated = true;
853 return updated;
856 Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin(
857 FullOriginMetadata& aFullOriginMetadata) {
858 MOZ_ASSERT(!NS_IsMainThread());
860 if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) {
861 QuotaManager* quotaManager = QuotaManager::Get();
862 MOZ_ASSERT(quotaManager);
864 QM_TRY_INSPECT(const auto& metadataFile,
865 quotaManager->GetOriginDirectory(aFullOriginMetadata));
867 QM_TRY(MOZ_TO_RESULT(
868 metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
870 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
871 metadataFile, GetLastModifiedTime));
873 // Need to convert from milliseconds to microseconds.
874 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
875 timestamp *= int64_t(PR_USEC_PER_MSEC);
877 aFullOriginMetadata.mLastAccessTime = timestamp;
879 return true;
882 return false;
885 } // namespace
887 BackgroundThreadObject::BackgroundThreadObject()
888 : mOwningThread(GetCurrentSerialEventTarget()) {
889 AssertIsOnOwningThread();
892 BackgroundThreadObject::BackgroundThreadObject(
893 nsISerialEventTarget* aOwningThread)
894 : mOwningThread(aOwningThread) {}
896 #ifdef DEBUG
898 void BackgroundThreadObject::AssertIsOnOwningThread() const {
899 AssertIsOnBackgroundThread();
900 MOZ_ASSERT(mOwningThread);
901 bool current;
902 MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
903 MOZ_ASSERT(current);
906 #endif // DEBUG
908 nsISerialEventTarget* BackgroundThreadObject::OwningThread() const {
909 MOZ_ASSERT(mOwningThread);
910 return mOwningThread;
913 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
914 // Get leaf of file path
915 for (const char* p = aFile; *p; ++p) {
916 if (*p == '/' && *(p + 1)) {
917 aFile = p + 1;
921 nsContentUtils::LogSimpleConsoleError(
922 NS_ConvertUTF8toUTF16(
923 nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
924 "quota"_ns,
925 false /* Quota Manager is not active in private browsing mode */,
926 true /* Quota Manager runs always in a chrome context */);
929 namespace {
931 bool gInvalidateQuotaCache = false;
932 StaticAutoPtr<nsString> gBasePath;
933 StaticAutoPtr<nsString> gStorageName;
934 StaticAutoPtr<nsCString> gBuildId;
936 #ifdef DEBUG
937 bool gQuotaManagerInitialized = false;
938 #endif
940 StaticRefPtr<QuotaManager> gInstance;
941 mozilla::Atomic<bool> gShutdown(false);
943 // A time stamp that can only be accessed on the main thread.
944 TimeStamp gLastOSWake;
946 // XXX Move to QuotaManager once NormalOriginOperationBase is declared in a
947 // separate and includable file.
948 using NormalOriginOpArray =
949 nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
950 StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
952 class StorageOperationBase {
953 protected:
954 struct OriginProps {
955 enum Type { eChrome, eContent, eObsolete, eInvalid };
957 NotNull<nsCOMPtr<nsIFile>> mDirectory;
958 nsString mLeafName;
959 nsCString mSpec;
960 OriginAttributes mAttrs;
961 int64_t mTimestamp;
962 OriginMetadata mOriginMetadata;
963 nsCString mOriginalSuffix;
965 LazyInitializedOnceEarlyDestructible<const PersistenceType>
966 mPersistenceType;
967 Type mType;
968 bool mNeedsRestore;
969 bool mNeedsRestore2;
970 bool mIgnore;
972 public:
973 explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
974 : mDirectory(std::move(aDirectory)),
975 mTimestamp(0),
976 mType(eContent),
977 mNeedsRestore(false),
978 mNeedsRestore2(false),
979 mIgnore(false) {}
981 template <typename PersistenceTypeFunc>
982 nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
985 nsTArray<OriginProps> mOriginProps;
987 nsCOMPtr<nsIFile> mDirectory;
989 public:
990 explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
991 AssertIsOnIOThread();
994 NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
996 protected:
997 virtual ~StorageOperationBase() = default;
999 nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
1000 nsACString& aGroup, nsACString& aOrigin,
1001 Nullable<bool>& aIsApp);
1003 // Upgrade helper to load the contents of ".metadata-v2" files from previous
1004 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
1005 // method, it is only intended to read current version ".metadata-v2" files.
1006 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
1007 // because our "storage.sqlite" lets us track the overall version of the
1008 // storage directory.
1009 nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
1010 nsACString& aSuffix, nsACString& aGroup,
1011 nsACString& aOrigin, bool& aIsApp);
1013 int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);
1015 nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
1018 * Rename the origin if the origin string generation from nsIPrincipal
1019 * changed. This consists of renaming the origin in the metadata files and
1020 * renaming the origin directory itself. For simplicity, the origin in
1021 * metadata files is not actually updated, but the metadata files are
1022 * recreated instead.
1024 * @param aOriginProps the properties of the origin to check.
1026 * @return whether origin was renamed.
1028 Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);
1030 nsresult ProcessOriginDirectories();
1032 virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
1035 class RepositoryOperationBase : public StorageOperationBase {
1036 public:
1037 explicit RepositoryOperationBase(nsIFile* aDirectory)
1038 : StorageOperationBase(aDirectory) {}
1040 nsresult ProcessRepository();
1042 protected:
1043 virtual ~RepositoryOperationBase() = default;
1045 template <typename UpgradeMethod>
1046 nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
1047 UpgradeMethod aMethod);
1049 private:
1050 virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;
1052 virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1053 bool* aRemoved) = 0;
1055 virtual nsresult PrepareClientDirectory(nsIFile* aFile,
1056 const nsAString& aLeafName,
1057 bool& aRemoved);
1060 class CreateOrUpgradeDirectoryMetadataHelper final
1061 : public RepositoryOperationBase {
1062 nsCOMPtr<nsIFile> mPermanentStorageDir;
1064 // The legacy PersistenceType, before the default repository introduction.
1065 enum class LegacyPersistenceType {
1066 Persistent = 0,
1067 Temporary
1068 // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
1069 // it here.
1072 LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;
1074 public:
1075 explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory)
1076 : RepositoryOperationBase(aDirectory) {}
1078 nsresult Init();
1080 private:
1081 Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile,
1082 const fallible_t&);
1084 PersistenceType PersistenceTypeFromLegacyPersistentSpec(
1085 const nsCString& aSpec);
1087 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
1089 nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
1091 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1092 bool* aRemoved) override;
1094 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1097 class UpgradeStorageHelperBase : public RepositoryOperationBase {
1098 LazyInitializedOnce<const PersistenceType> mPersistenceType;
1100 public:
1101 explicit UpgradeStorageHelperBase(nsIFile* aDirectory)
1102 : RepositoryOperationBase(aDirectory) {}
1104 nsresult Init();
1106 private:
1107 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
1110 class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase {
1111 public:
1112 explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory)
1113 : UpgradeStorageHelperBase(aDirectory) {}
1115 private:
1116 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1117 bool* aRemoved) override;
1119 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1122 class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase {
1123 public:
1124 explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory)
1125 : UpgradeStorageHelperBase(aDirectory) {}
1127 private:
1128 nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);
1131 * Remove the origin directory if appId is present in origin attributes.
1133 * @param aOriginProps the properties of the origin to check.
1135 * @return whether the origin directory was removed.
1137 Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps);
1139 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1140 bool* aRemoved) override;
1142 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1145 class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase {
1146 public:
1147 explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory)
1148 : UpgradeStorageHelperBase(aDirectory) {}
1150 private:
1151 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1152 bool* aRemoved) override;
1154 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1157 class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase {
1158 public:
1159 explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory)
1160 : UpgradeStorageHelperBase(aDirectory) {}
1162 private:
1163 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1164 bool* aRemoved) override;
1166 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1168 nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName,
1169 bool& aRemoved) override;
1172 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
1173 LazyInitializedOnce<const PersistenceType> mPersistenceType;
1175 public:
1176 explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory)
1177 : StorageOperationBase(aDirectory) {}
1179 nsresult Init();
1181 nsresult RestoreMetadata2File();
1183 private:
1184 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1187 Result<nsAutoString, nsresult> GetPathForStorage(
1188 nsIFile& aBaseDir, const nsAString& aStorageName) {
1189 QM_TRY_INSPECT(const auto& storageDir,
1190 CloneFileAndAppend(aBaseDir, aStorageName));
1192 QM_TRY_RETURN(
1193 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, storageDir, GetPath));
1196 int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) {
1197 AssertIsOnIOThread();
1199 class MOZ_STACK_CLASS Helper final {
1200 public:
1201 static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
1202 AssertIsOnIOThread();
1203 MOZ_ASSERT(aFile);
1204 MOZ_ASSERT(aTimestamp);
1206 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile));
1208 switch (dirEntryKind) {
1209 case nsIFileKind::ExistsAsDirectory:
1210 QM_TRY(CollectEachFile(
1211 *aFile,
1212 [&aTimestamp](const nsCOMPtr<nsIFile>& file)
1213 -> Result<mozilla::Ok, nsresult> {
1214 QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file, aTimestamp)));
1216 return Ok{};
1217 }));
1218 break;
1220 case nsIFileKind::ExistsAsFile: {
1221 QM_TRY_INSPECT(const auto& leafName,
1222 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aFile,
1223 GetLeafName));
1225 // Bug 1595445 will handle unknown files here.
1227 if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
1228 IsDotFile(leafName)) {
1229 return NS_OK;
1232 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
1233 aFile, GetLastModifiedTime));
1235 // Need to convert from milliseconds to microseconds.
1236 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
1237 timestamp *= int64_t(PR_USEC_PER_MSEC);
1239 if (timestamp > *aTimestamp) {
1240 *aTimestamp = timestamp;
1242 break;
1245 case nsIFileKind::DoesNotExist:
1246 // Ignore files that got removed externally while iterating.
1247 break;
1250 return NS_OK;
1254 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
1255 return PR_Now();
1258 int64_t timestamp = INT64_MIN;
1259 nsresult rv = Helper::GetLastModifiedTime(&aFile, &timestamp);
1260 if (NS_FAILED(rv)) {
1261 timestamp = PR_Now();
1264 // XXX if there were no suitable files for getting last modified time
1265 // (timestamp is still set to INT64_MIN), we should return the current time
1266 // instead of returning INT64_MIN.
1268 return timestamp;
1271 // Returns a bool indicating whether the directory was newly created.
1272 Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) {
1273 AssertIsOnIOThread();
1275 // Callers call this function without checking if the directory already
1276 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
1277 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
1278 // reports.
1279 QM_TRY_INSPECT(const auto& exists,
1280 QM_OR_ELSE_LOG_VERBOSE_IF(
1281 // Expression.
1282 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Create,
1283 nsIFile::DIRECTORY_TYPE, 0755,
1284 /* aSkipAncestors = */ false)
1285 .map([](Ok) { return false; }),
1286 // Predicate.
1287 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
1288 // Fallback.
1289 ErrToOk<true>));
1291 if (exists) {
1292 QM_TRY_INSPECT(const bool& isDirectory,
1293 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
1294 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
1297 return !exists;
1300 void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
1301 aJarPrefix.Truncate();
1303 // Fallback.
1304 if (!aInIsolatedMozBrowser) {
1305 return;
1308 // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
1309 // 1320404).
1310 // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
1311 aJarPrefix.AppendInt(0); // TODO: this is the appId, to be removed.
1312 aJarPrefix.Append('+');
1313 aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
1314 aJarPrefix.Append('+');
1317 // This method computes and returns our best guess for the temporary storage
1318 // limit (in bytes), based on disk capacity.
1319 Result<uint64_t, nsresult> GetTemporaryStorageLimit(nsIFile& aStorageDir) {
1320 // The fixed limit pref can be used to override temporary storage limit
1321 // calculation.
1322 if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
1323 return static_cast<uint64_t>(
1324 StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
1325 1024;
1328 constexpr int64_t teraByte = (1024LL * 1024LL * 1024LL * 1024LL);
1329 constexpr int64_t maxAllowedCapacity = 8LL * teraByte;
1331 // Check for disk capacity of user's device on which storage directory lives.
1332 int64_t diskCapacity = maxAllowedCapacity;
1334 // Log error when default disk capacity is returned due to the error
1335 QM_WARNONLY_TRY(MOZ_TO_RESULT(aStorageDir.GetDiskCapacity(&diskCapacity)));
1337 MOZ_ASSERT(diskCapacity >= 0LL);
1339 // Allow temporary storage to consume up to 50% of disk capacity.
1340 int64_t capacityLimit = diskCapacity / 2LL;
1342 // If the disk capacity reported by the operating system is very
1343 // large and potentially incorrect due to hardware issues,
1344 // a hardcoded limit is supplied instead.
1345 QM_WARNONLY_TRY(
1346 OkIf(capacityLimit < maxAllowedCapacity),
1347 ([&capacityLimit](const auto&) { capacityLimit = maxAllowedCapacity; }));
1349 return capacityLimit;
1352 bool IsOriginUnaccessed(const FullOriginMetadata& aFullOriginMetadata,
1353 const int64_t aRecentTime) {
1354 if (aFullOriginMetadata.mLastAccessTime > aRecentTime) {
1355 return false;
1358 return (aRecentTime - aFullOriginMetadata.mLastAccessTime) / PR_USEC_PER_SEC >
1359 StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec();
1362 } // namespace
1364 /*******************************************************************************
1365 * Exported functions
1366 ******************************************************************************/
1368 void InitializeQuotaManager() {
1369 MOZ_ASSERT(XRE_IsParentProcess());
1370 MOZ_ASSERT(NS_IsMainThread());
1371 MOZ_ASSERT(!gQuotaManagerInitialized);
1373 if (!QuotaManager::IsRunningGTests()) {
1374 // These services have to be started on the main thread currently.
1375 const nsCOMPtr<mozIStorageService> ss =
1376 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
1377 QM_WARNONLY_TRY(OkIf(ss));
1379 RefPtr<net::ExtensionProtocolHandler> extensionProtocolHandler =
1380 net::ExtensionProtocolHandler::GetSingleton();
1381 QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler));
1383 IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
1384 QM_WARNONLY_TRY(MOZ_TO_RESULT(mgr));
1387 QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));
1389 #ifdef DEBUG
1390 gQuotaManagerInitialized = true;
1391 #endif
1394 void InitializeScopedLogExtraInfo() {
1395 #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
1396 ScopedLogExtraInfo::Initialize();
1397 #endif
1400 bool RecvShutdownQuotaManager() {
1401 AssertIsOnBackgroundThread();
1403 // If we are already in shutdown, don't call ShutdownInstance()
1404 // again and return true immediately. We shall see this incident
1405 // in Telemetry.
1406 // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124)
1407 // XXX todo: Active QM_TRY context for shutdown (Bug 1735170)
1408 QM_TRY(OkIf(!gShutdown), true);
1410 QuotaManager::ShutdownInstance();
1412 return true;
1415 QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;
1417 // static
1418 nsresult QuotaManager::Observer::Initialize() {
1419 MOZ_ASSERT(NS_IsMainThread());
1421 RefPtr<Observer> observer = new Observer();
1423 nsresult rv = observer->Init();
1424 if (NS_WARN_IF(NS_FAILED(rv))) {
1425 return rv;
1428 sInstance = observer;
1430 return NS_OK;
1433 // static
1434 nsIObserver* QuotaManager::Observer::GetInstance() {
1435 MOZ_ASSERT(NS_IsMainThread());
1437 return sInstance;
1440 // static
1441 void QuotaManager::Observer::ShutdownCompleted() {
1442 MOZ_ASSERT(NS_IsMainThread());
1443 MOZ_ASSERT(sInstance);
1445 sInstance->mShutdownComplete = true;
1448 nsresult QuotaManager::Observer::Init() {
1449 MOZ_ASSERT(NS_IsMainThread());
1451 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1452 if (NS_WARN_IF(!obs)) {
1453 return NS_ERROR_FAILURE;
1456 // XXX: Improve the way that we remove observer in failure cases.
1457 nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
1458 if (NS_WARN_IF(NS_FAILED(rv))) {
1459 return rv;
1462 rv = obs->AddObserver(this, kProfileDoChangeTopic, false);
1463 if (NS_WARN_IF(NS_FAILED(rv))) {
1464 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1465 return rv;
1468 rv = obs->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
1469 if (NS_WARN_IF(NS_FAILED(rv))) {
1470 obs->RemoveObserver(this, kProfileDoChangeTopic);
1471 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1472 return rv;
1475 rv = obs->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
1476 if (NS_WARN_IF(NS_FAILED(rv))) {
1477 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
1478 obs->RemoveObserver(this, kProfileDoChangeTopic);
1479 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1480 return rv;
1483 rv = obs->AddObserver(this, kPrivateBrowsingObserverTopic, false);
1484 if (NS_WARN_IF(NS_FAILED(rv))) {
1485 obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
1486 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
1487 obs->RemoveObserver(this, kProfileDoChangeTopic);
1488 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1489 return rv;
1492 return NS_OK;
1495 nsresult QuotaManager::Observer::Shutdown() {
1496 MOZ_ASSERT(NS_IsMainThread());
1498 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1499 if (NS_WARN_IF(!obs)) {
1500 return NS_ERROR_FAILURE;
1503 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kPrivateBrowsingObserverTopic));
1504 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC));
1505 MOZ_ALWAYS_SUCCEEDS(
1506 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
1507 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
1508 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
1510 sInstance = nullptr;
1512 // In general, the instance will have died after the latter removal call, so
1513 // it's not safe to do anything after that point.
1514 // However, Shutdown is currently called from Observe which is called by the
1515 // Observer Service which holds a strong reference to the observer while the
1516 // Observe method is being called.
1518 return NS_OK;
1521 NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)
1523 NS_IMETHODIMP
1524 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
1525 const char16_t* aData) {
1526 MOZ_ASSERT(NS_IsMainThread());
1528 nsresult rv;
1530 if (!strcmp(aTopic, kProfileDoChangeTopic)) {
1531 if (NS_WARN_IF(gBasePath)) {
1532 NS_WARNING(
1533 "profile-before-change-qm must precede repeated "
1534 "profile-do-change!");
1535 return NS_OK;
1538 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, true);
1540 gBasePath = new nsString();
1542 nsCOMPtr<nsIFile> baseDir;
1543 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
1544 getter_AddRefs(baseDir));
1545 if (NS_FAILED(rv)) {
1546 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1547 getter_AddRefs(baseDir));
1549 if (NS_WARN_IF(NS_FAILED(rv))) {
1550 return rv;
1553 rv = baseDir->GetPath(*gBasePath);
1554 if (NS_WARN_IF(NS_FAILED(rv))) {
1555 return rv;
1558 #ifdef XP_WIN
1559 // Annotate if our profile lives on a network resource.
1560 bool isNetworkPath = PathIsNetworkPathW(gBasePath->get());
1561 CrashReporter::RecordAnnotationBool(
1562 CrashReporter::Annotation::QuotaManagerStorageIsNetworkResource,
1563 isNetworkPath);
1564 #endif
1566 gStorageName = new nsString();
1568 rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName);
1569 if (NS_FAILED(rv)) {
1570 *gStorageName = kStorageName;
1573 gBuildId = new nsCString();
1575 nsCOMPtr<nsIPlatformInfo> platformInfo =
1576 do_GetService("@mozilla.org/xre/app-info;1");
1577 if (NS_WARN_IF(!platformInfo)) {
1578 return NS_ERROR_FAILURE;
1581 rv = platformInfo->GetPlatformBuildID(*gBuildId);
1582 if (NS_WARN_IF(NS_FAILED(rv))) {
1583 return rv;
1586 return NS_OK;
1589 if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
1590 if (NS_WARN_IF(!gBasePath)) {
1591 NS_WARNING("profile-do-change must precede profile-before-change-qm!");
1592 return NS_OK;
1595 // mPendingProfileChange is our re-entrancy guard (the nested event loop
1596 // below may cause re-entrancy).
1597 if (mPendingProfileChange) {
1598 return NS_OK;
1601 AutoRestore<bool> pending(mPendingProfileChange);
1602 mPendingProfileChange = true;
1604 mShutdownComplete = false;
1606 PBackgroundChild* backgroundActor =
1607 BackgroundChild::GetOrCreateForCurrentThread();
1608 if (NS_WARN_IF(!backgroundActor)) {
1609 return NS_ERROR_FAILURE;
1612 if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
1613 return NS_ERROR_FAILURE;
1616 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
1617 "QuotaManager::Observer::Observe profile-before-change-qm"_ns,
1618 [&]() { return mShutdownComplete; }));
1620 gBasePath = nullptr;
1622 gStorageName = nullptr;
1624 gBuildId = nullptr;
1626 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, false);
1628 return NS_OK;
1631 if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) {
1632 auto* const quotaManagerService = QuotaManagerService::GetOrCreate();
1633 if (NS_WARN_IF(!quotaManagerService)) {
1634 return NS_ERROR_FAILURE;
1637 nsCOMPtr<nsIQuotaRequest> request;
1638 rv = quotaManagerService->ClearStoragesForPrivateBrowsing(
1639 nsGetterAddRefs(request));
1640 if (NS_WARN_IF(NS_FAILED(rv))) {
1641 return rv;
1644 return NS_OK;
1647 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
1648 rv = Shutdown();
1649 if (NS_WARN_IF(NS_FAILED(rv))) {
1650 return rv;
1653 return NS_OK;
1656 if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
1657 gLastOSWake = TimeStamp::Now();
1659 return NS_OK;
1662 NS_WARNING("Unknown observer topic!");
1663 return NS_OK;
1666 /*******************************************************************************
1667 * Quota manager
1668 ******************************************************************************/
1670 QuotaManager::QuotaManager(const nsAString& aBasePath,
1671 const nsAString& aStorageName)
1672 : mQuotaMutex("QuotaManager.mQuotaMutex"),
1673 mBasePath(aBasePath),
1674 mStorageName(aStorageName),
1675 mTemporaryStorageUsage(0),
1676 mNextDirectoryLockId(0),
1677 mShutdownStorageOpCount(0),
1678 mStorageInitialized(false),
1679 mTemporaryStorageInitialized(false),
1680 mTemporaryStorageInitializedInternal(false),
1681 mCacheUsable(false) {
1682 AssertIsOnOwningThread();
1683 MOZ_ASSERT(!gInstance);
1686 QuotaManager::~QuotaManager() {
1687 AssertIsOnOwningThread();
1688 MOZ_ASSERT(!gInstance || gInstance == this);
1691 // static
1692 nsresult QuotaManager::Initialize() {
1693 MOZ_ASSERT(NS_IsMainThread());
1695 nsresult rv = Observer::Initialize();
1696 if (NS_WARN_IF(NS_FAILED(rv))) {
1697 return rv;
1700 return NS_OK;
1703 // static
1704 Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult>
1705 QuotaManager::GetOrCreate() {
1706 AssertIsOnBackgroundThread();
1708 if (gInstance) {
1709 return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance});
1712 QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) {
1713 NS_WARNING(
1714 "Trying to create QuotaManager before profile-do-change! "
1715 "Forgot to call do_get_profile()?");
1718 QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) {
1719 MOZ_ASSERT(false,
1720 "Trying to create QuotaManager after profile-before-change-qm!");
1723 auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName);
1725 QM_TRY(MOZ_TO_RESULT(instance->Init()));
1727 gInstance = instance;
1729 // Do this before clients have a chance to acquire a directory lock for the
1730 // private repository.
1731 gInstance->ClearPrivateRepository();
1733 return WrapMovingNotNullUnchecked(std::move(instance));
1736 Result<Ok, nsresult> QuotaManager::EnsureCreated() {
1737 AssertIsOnBackgroundThread();
1739 QM_TRY_RETURN(GetOrCreate().map([](const auto& res) { return Ok{}; }))
1742 // static
1743 QuotaManager* QuotaManager::Get() {
1744 // Does not return an owning reference.
1745 return gInstance;
1748 // static
1749 nsIObserver* QuotaManager::GetObserver() {
1750 MOZ_ASSERT(NS_IsMainThread());
1752 return Observer::GetInstance();
1755 // static
1756 bool QuotaManager::IsShuttingDown() { return gShutdown; }
1758 // static
1759 void QuotaManager::ShutdownInstance() {
1760 AssertIsOnBackgroundThread();
1762 if (gInstance) {
1763 auto recordTimeDeltaHelper =
1764 MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_SHUTDOWN_TIME_V0);
1766 recordTimeDeltaHelper->Start();
1768 gInstance->Shutdown();
1770 recordTimeDeltaHelper->End();
1772 gInstance = nullptr;
1773 } else {
1774 // If we were never initialized, just set the flag to avoid late creation.
1775 gShutdown = true;
1778 RefPtr<Runnable> runnable =
1779 NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
1780 []() { Observer::ShutdownCompleted(); });
1781 MOZ_ASSERT(runnable);
1783 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
1786 // static
1787 void QuotaManager::Reset() {
1788 AssertIsOnBackgroundThread();
1789 MOZ_ASSERT(!gInstance);
1790 MOZ_ASSERT(gShutdown);
1792 gShutdown = false;
1795 // static
1796 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
1797 return mozilla::dom::quota::IsOSMetadata(aFileName);
1800 // static
1801 bool QuotaManager::IsDotFile(const nsAString& aFileName) {
1802 return mozilla::dom::quota::IsDotFile(aFileName);
1805 void QuotaManager::RegisterNormalOriginOp(
1806 NormalOriginOperationBase& aNormalOriginOp) {
1807 AssertIsOnBackgroundThread();
1809 if (!gNormalOriginOps) {
1810 gNormalOriginOps = new NormalOriginOpArray();
1813 gNormalOriginOps->AppendElement(&aNormalOriginOp);
1816 void QuotaManager::UnregisterNormalOriginOp(
1817 NormalOriginOperationBase& aNormalOriginOp) {
1818 AssertIsOnBackgroundThread();
1819 MOZ_ASSERT(gNormalOriginOps);
1821 gNormalOriginOps->RemoveElement(&aNormalOriginOp);
1823 if (gNormalOriginOps->IsEmpty()) {
1824 gNormalOriginOps = nullptr;
1828 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) {
1829 AssertIsOnOwningThread();
1831 mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock));
1833 if (aLock.ShouldUpdateLockIdTable()) {
1834 MutexAutoLock lock(mQuotaMutex);
1836 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id()));
1837 mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(),
1838 WrapNotNullUnchecked(&aLock));
1841 if (aLock.ShouldUpdateLockTable()) {
1842 DirectoryLockTable& directoryLockTable =
1843 GetDirectoryLockTable(aLock.GetPersistenceType());
1845 // XXX It seems that the contents of the array are never actually used, we
1846 // just use that like an inefficient use counter. Can't we just change
1847 // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
1848 directoryLockTable
1849 .LookupOrInsertWith(
1850 aLock.Origin(),
1851 [this, &aLock] {
1852 if (!IsShuttingDown()) {
1853 UpdateOriginAccessTime(aLock.GetPersistenceType(),
1854 aLock.OriginMetadata());
1856 return MakeUnique<nsTArray<NotNull<DirectoryLockImpl*>>>();
1858 ->AppendElement(WrapNotNullUnchecked(&aLock));
1861 aLock.SetRegistered(true);
1864 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) {
1865 AssertIsOnOwningThread();
1867 MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock));
1869 if (aLock.ShouldUpdateLockIdTable()) {
1870 MutexAutoLock lock(mQuotaMutex);
1872 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id()));
1873 mDirectoryLockIdTable.Remove(aLock.Id());
1876 if (aLock.ShouldUpdateLockTable()) {
1877 DirectoryLockTable& directoryLockTable =
1878 GetDirectoryLockTable(aLock.GetPersistenceType());
1880 // ClearDirectoryLockTables may have been called, so the element or entire
1881 // array may not exist anymre.
1882 nsTArray<NotNull<DirectoryLockImpl*>>* array;
1883 if (directoryLockTable.Get(aLock.Origin(), &array) &&
1884 array->RemoveElement(&aLock) && array->IsEmpty()) {
1885 directoryLockTable.Remove(aLock.Origin());
1887 if (!IsShuttingDown()) {
1888 UpdateOriginAccessTime(aLock.GetPersistenceType(),
1889 aLock.OriginMetadata());
1894 aLock.SetRegistered(false);
1897 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) {
1898 AssertIsOnOwningThread();
1900 mPendingDirectoryLocks.AppendElement(&aLock);
1903 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) {
1904 AssertIsOnOwningThread();
1906 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock));
1909 uint64_t QuotaManager::CollectOriginsForEviction(
1910 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
1911 AssertIsOnOwningThread();
1912 MOZ_ASSERT(aLocks.IsEmpty());
1914 // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
1915 // or maybe a generalization if that.
1917 struct MOZ_STACK_CLASS Helper final {
1918 static void GetInactiveOriginInfos(
1919 const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos,
1920 const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks,
1921 OriginInfosFlatTraversable& aInactiveOriginInfos) {
1922 for (const auto& originInfo : aOriginInfos) {
1923 MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
1924 PERSISTENCE_TYPE_PERSISTENT);
1926 if (originInfo->LockedPersisted()) {
1927 continue;
1930 // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a
1931 // moz-extension origin, unlike websites (which may more likely using
1932 // the local data as a cache but still able to retrieve the same data
1933 // from the server side) extensions do not have the same data stored
1934 // anywhere else and evicting the data would result into potential data
1935 // loss for the users.
1937 // Also, unlike a website the extensions are explicitly installed and
1938 // uninstalled by the user and all data associated to the extension
1939 // principal will be completely removed once the addon is uninstalled.
1940 if (originInfo->mGroupInfo->mPersistenceType !=
1941 PERSISTENCE_TYPE_TEMPORARY &&
1942 originInfo->IsExtensionOrigin()) {
1943 continue;
1946 const auto originScope = OriginScope::FromOrigin(originInfo->mOrigin);
1948 const bool match =
1949 std::any_of(aLocks.begin(), aLocks.end(),
1950 [&originScope](const DirectoryLockImpl* const lock) {
1951 return originScope.Matches(lock->GetOriginScope());
1954 if (!match) {
1955 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count(),
1956 "Inactive origin shouldn't have open files!");
1957 aInactiveOriginInfos.InsertElementSorted(
1958 originInfo, OriginInfoAccessTimeComparator());
1964 // Split locks into separate arrays and filter out locks for persistent
1965 // storage, they can't block us.
1966 auto [temporaryStorageLocks, defaultStorageLocks,
1967 privateStorageLocks] = [this] {
1968 nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks;
1969 nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks;
1970 nsTArray<NotNull<const DirectoryLockImpl*>> privateStorageLocks;
1972 for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) {
1973 const Nullable<PersistenceType>& persistenceType =
1974 lock->NullablePersistenceType();
1976 if (persistenceType.IsNull()) {
1977 temporaryStorageLocks.AppendElement(lock);
1978 defaultStorageLocks.AppendElement(lock);
1979 } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
1980 temporaryStorageLocks.AppendElement(lock);
1981 } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
1982 defaultStorageLocks.AppendElement(lock);
1983 } else if (persistenceType.Value() == PERSISTENCE_TYPE_PRIVATE) {
1984 privateStorageLocks.AppendElement(lock);
1985 } else {
1986 MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
1988 // Do nothing here, persistent origins don't need to be collected ever.
1992 return std::make_tuple(std::move(temporaryStorageLocks),
1993 std::move(defaultStorageLocks),
1994 std::move(privateStorageLocks));
1995 }();
1997 // Enumerate and process inactive origins. This must be protected by the
1998 // mutex.
1999 MutexAutoLock lock(mQuotaMutex);
2001 const auto [inactiveOrigins, sizeToBeFreed] =
2002 [this, &temporaryStorageLocks = temporaryStorageLocks,
2003 &defaultStorageLocks = defaultStorageLocks,
2004 &privateStorageLocks = privateStorageLocks, aMinSizeToBeFreed] {
2005 nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins;
2006 for (const auto& entry : mGroupInfoPairs) {
2007 const auto& pair = entry.GetData();
2009 MOZ_ASSERT(!entry.GetKey().IsEmpty());
2010 MOZ_ASSERT(pair);
2012 RefPtr<GroupInfo> groupInfo =
2013 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2014 if (groupInfo) {
2015 Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
2016 temporaryStorageLocks,
2017 inactiveOrigins);
2020 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
2021 if (groupInfo) {
2022 Helper::GetInactiveOriginInfos(
2023 groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins);
2026 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
2027 if (groupInfo) {
2028 Helper::GetInactiveOriginInfos(
2029 groupInfo->mOriginInfos, privateStorageLocks, inactiveOrigins);
2033 #ifdef DEBUG
2034 // Make sure the array is sorted correctly.
2035 const bool inactiveOriginsSorted =
2036 std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(),
2037 [](const auto& lhs, const auto& rhs) {
2038 return lhs->mAccessTime < rhs->mAccessTime;
2040 MOZ_ASSERT(inactiveOriginsSorted);
2041 #endif
2043 // Create a list of inactive and the least recently used origins
2044 // whose aggregate size is greater or equals the minimal size to be
2045 // freed.
2046 uint64_t sizeToBeFreed = 0;
2047 for (uint32_t count = inactiveOrigins.Length(), index = 0;
2048 index < count; index++) {
2049 if (sizeToBeFreed >= aMinSizeToBeFreed) {
2050 inactiveOrigins.TruncateLength(index);
2051 break;
2054 sizeToBeFreed += inactiveOrigins[index]->LockedUsage();
2057 return std::pair(std::move(inactiveOrigins), sizeToBeFreed);
2058 }();
2060 if (sizeToBeFreed >= aMinSizeToBeFreed) {
2061 // Success, add directory locks for these origins, so any other
2062 // operations for them will be delayed (until origin eviction is finalized).
2064 for (const auto& originInfo : inactiveOrigins) {
2065 auto lock = DirectoryLockImpl::CreateForEviction(
2066 WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType,
2067 originInfo->FlattenToOriginMetadata());
2069 lock->AcquireImmediately();
2071 aLocks.AppendElement(lock.forget());
2074 return sizeToBeFreed;
2077 return 0;
2080 nsresult QuotaManager::Init() {
2081 AssertIsOnOwningThread();
2083 #ifdef XP_WIN
2084 CacheUseDOSDevicePathSyntaxPrefValue();
2085 #endif
2087 QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath));
2089 QM_TRY_UNWRAP(
2090 do_Init(mIndexedDBPath),
2091 GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME)));
2093 QM_TRY(MOZ_TO_RESULT(baseDir->Append(mStorageName)));
2095 QM_TRY_UNWRAP(do_Init(mStoragePath),
2096 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, baseDir, GetPath));
2098 QM_TRY_UNWRAP(
2099 do_Init(mStorageArchivesPath),
2100 GetPathForStorage(*baseDir, nsLiteralString(ARCHIVES_DIRECTORY_NAME)));
2102 QM_TRY_UNWRAP(
2103 do_Init(mPermanentStoragePath),
2104 GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME)));
2106 QM_TRY_UNWRAP(
2107 do_Init(mTemporaryStoragePath),
2108 GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
2110 QM_TRY_UNWRAP(
2111 do_Init(mDefaultStoragePath),
2112 GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
2114 QM_TRY_UNWRAP(
2115 do_Init(mPrivateStoragePath),
2116 GetPathForStorage(*baseDir, nsLiteralString(PRIVATE_DIRECTORY_NAME)));
2118 QM_TRY_UNWRAP(
2119 do_Init(mToBeRemovedStoragePath),
2120 GetPathForStorage(*baseDir, nsLiteralString(TOBEREMOVED_DIRECTORY_NAME)));
2122 QM_TRY_UNWRAP(do_Init(mIOThread),
2123 MOZ_TO_RESULT_INVOKE_TYPED(
2124 nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
2125 "QuotaManager IO"));
2127 static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
2128 Client::FILESYSTEM == 3 && Client::LS == 4 &&
2129 Client::TYPE_MAX == 5,
2130 "Fix the registration!");
2132 // Register clients.
2133 auto clients = decltype(mClients)::ValueType{};
2134 clients.AppendElement(indexedDB::CreateQuotaClient());
2135 clients.AppendElement(cache::CreateQuotaClient());
2136 clients.AppendElement(simpledb::CreateQuotaClient());
2137 clients.AppendElement(fs::CreateQuotaClient());
2138 if (NextGenLocalStorageEnabled()) {
2139 clients.AppendElement(localstorage::CreateQuotaClient());
2140 } else {
2141 clients.SetLength(Client::TypeMax());
2144 mClients.init(std::move(clients));
2146 MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX,
2147 "Should be using an auto array with correct capacity!");
2149 mAllClientTypes.init(ClientTypesArray{
2150 Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB,
2151 Client::Type::FILESYSTEM, Client::Type::LS});
2152 mAllClientTypesExceptLS.init(
2153 ClientTypesArray{Client::Type::IDB, Client::Type::DOMCACHE,
2154 Client::Type::SDB, Client::Type::FILESYSTEM});
2156 return NS_OK;
2159 // static
2160 void QuotaManager::MaybeRecordQuotaClientShutdownStep(
2161 const Client::Type aClientType, const nsACString& aStepDescription) {
2162 // Callable on any thread.
2164 auto* const quotaManager = QuotaManager::Get();
2165 MOZ_DIAGNOSTIC_ASSERT(quotaManager);
2167 if (quotaManager->IsShuttingDown()) {
2168 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
2172 // static
2173 void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
2174 const Client::Type aClientType, const nsACString& aStepDescription) {
2175 // Callable on any thread.
2177 auto* const quotaManager = QuotaManager::Get();
2179 if (quotaManager && quotaManager->IsShuttingDown()) {
2180 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
2184 void QuotaManager::RecordQuotaManagerShutdownStep(
2185 const nsACString& aStepDescription) {
2186 // Callable on any thread.
2187 MOZ_ASSERT(IsShuttingDown());
2189 RecordShutdownStep(Nothing{}, aStepDescription);
2192 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
2193 const nsACString& aStepDescription) {
2194 // Callable on any thread.
2196 if (IsShuttingDown()) {
2197 RecordQuotaManagerShutdownStep(aStepDescription);
2201 void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType,
2202 const nsACString& aStepDescription) {
2203 MOZ_ASSERT(IsShuttingDown());
2205 const TimeDuration elapsedSinceShutdownStart =
2206 TimeStamp::NowLoRes() - *mShutdownStartedAt;
2208 const auto stepString =
2209 nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(),
2210 nsPromiseFlatCString(aStepDescription).get());
2212 if (aClientType) {
2213 AssertIsOnBackgroundThread();
2215 mShutdownSteps[*aClientType].Append(stepString + "\n"_ns);
2216 } else {
2217 // Callable on any thread.
2218 MutexAutoLock lock(mQuotaMutex);
2220 mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns);
2223 #ifdef DEBUG
2224 // XXX Probably this isn't the mechanism that should be used here.
2226 NS_DebugBreak(
2227 NS_DEBUG_WARNING,
2228 nsAutoCString(aClientType ? Client::TypeToText(*aClientType)
2229 : "quota manager"_ns + " shutdown step"_ns)
2230 .get(),
2231 stepString.get(), __FILE__, __LINE__);
2232 #endif
2235 void QuotaManager::Shutdown() {
2236 AssertIsOnOwningThread();
2237 MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
2239 // Define some local helper functions
2241 auto flagShutdownStarted = [this]() {
2242 mShutdownStartedAt.init(TimeStamp::NowLoRes());
2244 // Setting this flag prevents the service from being recreated and prevents
2245 // further storages from being created.
2246 gShutdown = true;
2249 nsCOMPtr<nsITimer> crashBrowserTimer;
2251 auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) {
2252 auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
2254 nsCString annotation;
2256 for (Client::Type type : quotaManager->AllClientTypes()) {
2257 auto& quotaClient = *(*quotaManager->mClients)[type];
2259 if (!quotaClient.IsShutdownCompleted()) {
2260 annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
2261 Client::TypeToText(type).get(),
2262 quotaClient.GetShutdownStatus().get(),
2263 quotaManager->mShutdownSteps[type].get());
2267 if (gNormalOriginOps) {
2268 annotation.AppendPrintf("QM: %zu normal origin ops pending\n",
2269 gNormalOriginOps->Length());
2270 #ifdef QM_COLLECTING_OPERATION_TELEMETRY
2271 for (const auto& op : *gNormalOriginOps) {
2272 annotation.AppendPrintf("Op: %s pending\n", op->Name());
2274 #endif
2277 MutexAutoLock lock(quotaManager->mQuotaMutex);
2279 annotation.AppendPrintf("Intermediate steps:\n%s\n",
2280 quotaManager->mQuotaManagerShutdownSteps.get());
2283 CrashReporter::RecordAnnotationNSCString(
2284 CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation);
2286 MOZ_CRASH("Quota manager shutdown timed out");
2289 auto startCrashBrowserTimer = [&]() {
2290 crashBrowserTimer = NS_NewTimer();
2291 MOZ_ASSERT(crashBrowserTimer);
2292 if (crashBrowserTimer) {
2293 RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns);
2294 MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback(
2295 crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS,
2296 nsITimer::TYPE_ONE_SHOT,
2297 "quota::QuotaManager::Shutdown::crashBrowserTimer"));
2301 auto stopCrashBrowserTimer = [&]() {
2302 if (crashBrowserTimer) {
2303 RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns);
2304 QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel()));
2308 auto initiateShutdownWorkThreads = [this]() {
2309 RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns);
2310 bool needsToWait = false;
2311 for (Client::Type type : AllClientTypes()) {
2312 // Clients are supposed to also AbortAllOperations from this point on
2313 // to speed up shutdown, if possible. Thus pending operations
2314 // might not be executed anymore.
2315 needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
2318 return needsToWait;
2321 nsCOMPtr<nsITimer> killActorsTimer;
2323 auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) {
2324 auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
2326 quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns);
2328 // XXX: This abort is a workaround to unblock shutdown, which
2329 // ought to be removed by bug 1682326. We probably need more
2330 // checks to immediately abort new operations during
2331 // shutdown.
2332 quotaManager->GetClient(Client::IDB)->AbortAllOperations();
2334 for (Client::Type type : quotaManager->AllClientTypes()) {
2335 quotaManager->GetClient(type)->ForceKillActors();
2339 auto startKillActorsTimer = [&]() {
2340 killActorsTimer = NS_NewTimer();
2341 MOZ_ASSERT(killActorsTimer);
2342 if (killActorsTimer) {
2343 RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns);
2344 MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback(
2345 killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
2346 nsITimer::TYPE_ONE_SHOT,
2347 "quota::QuotaManager::Shutdown::killActorsTimer"));
2351 auto stopKillActorsTimer = [&]() {
2352 if (killActorsTimer) {
2353 RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns);
2354 QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel()));
2358 auto isAllClientsShutdownComplete = [this] {
2359 return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
2360 [&self = *this](const auto type) {
2361 return (*self.mClients)[type]->IsShutdownCompleted();
2365 auto shutdownAndJoinWorkThreads = [this]() {
2366 RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns);
2367 for (Client::Type type : AllClientTypes()) {
2368 (*mClients)[type]->FinalizeShutdownWorkThreads();
2372 auto shutdownAndJoinIOThread = [this]() {
2373 RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns);
2375 // Make sure to join with our IO thread.
2376 QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
2379 auto invalidatePendingDirectoryLocks = [this]() {
2380 RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns);
2381 for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
2382 lock->Invalidate();
2386 // Body of the function
2388 ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContextTainted,
2389 "dom::quota::QuotaManager::Shutdown"_ns};
2391 // We always need to ensure that firefox does not shutdown with a private
2392 // repository still on disk. They are ideally cleaned up on PBM session end
2393 // but, in some cases like PBM autostart (i.e.
2394 // browser.privatebrowsing.autostart), private repository could only be
2395 // cleaned up on shutdown. ClearPrivateRepository below runs a async op and is
2396 // better to do it before we run the ShutdownStorageOp since it expects all
2397 // cleanup operations to be done by that point. We don't need to use the
2398 // returned promise here because `ClearPrivateRepository` registers the
2399 // underlying `ClearPrivateRepositoryOp` in `gNormalOriginOps`.
2400 ClearPrivateRepository();
2402 // This must be called before `flagShutdownStarted`, it would fail otherwise.
2403 // `ShutdownStorageOp` needs to acquire an exclusive directory lock over
2404 // entire <profile>/storage which will abort any existing operations and wait
2405 // for all existing directory locks to be released. So the shutdown operation
2406 // will effectively run after all existing operations.
2407 // Similar, to ClearPrivateRepository operation above, ShutdownStorageOp also
2408 // registers it's operation in `gNormalOriginOps` so we don't need to assign
2409 // returned promise.
2410 ShutdownStorage();
2412 flagShutdownStarted();
2414 startCrashBrowserTimer();
2416 // XXX: StopIdleMaintenance now just notifies all clients to abort any
2417 // maintenance work.
2418 // This could be done as part of QuotaClient::AbortAllOperations.
2419 StopIdleMaintenance();
2421 // XXX In theory, we could simplify the code below (and also the `Client`
2422 // interface) by removing the `initiateShutdownWorkThreads` and
2423 // `isAllClientsShutdownComplete` calls because it should be sufficient
2424 // to rely on `ShutdownStorage` to abort all existing operations and to
2425 // wait for all existing directory locks to be released as well.
2427 const bool needsToWait =
2428 initiateShutdownWorkThreads() || static_cast<bool>(gNormalOriginOps);
2430 // If any clients cannot shutdown immediately, spin the event loop while we
2431 // wait on all the threads to close.
2432 if (needsToWait) {
2433 startKillActorsTimer();
2435 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
2436 "QuotaManager::Shutdown"_ns, [isAllClientsShutdownComplete]() {
2437 return !gNormalOriginOps && isAllClientsShutdownComplete();
2438 }));
2440 stopKillActorsTimer();
2443 shutdownAndJoinWorkThreads();
2445 shutdownAndJoinIOThread();
2447 invalidatePendingDirectoryLocks();
2449 stopCrashBrowserTimer();
2452 void QuotaManager::InitQuotaForOrigin(
2453 const FullOriginMetadata& aFullOriginMetadata,
2454 const ClientUsageArray& aClientUsages, uint64_t aUsageBytes) {
2455 AssertIsOnIOThread();
2456 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
2458 MutexAutoLock lock(mQuotaMutex);
2460 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
2461 aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix,
2462 aFullOriginMetadata.mGroup);
2464 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
2465 groupInfo, aFullOriginMetadata.mOrigin,
2466 aFullOriginMetadata.mStorageOrigin, aFullOriginMetadata.mIsPrivate,
2467 aClientUsages, aUsageBytes, aFullOriginMetadata.mLastAccessTime,
2468 aFullOriginMetadata.mPersisted,
2469 /* aDirectoryExists */ true));
2472 void QuotaManager::EnsureQuotaForOrigin(const OriginMetadata& aOriginMetadata) {
2473 AssertIsOnIOThread();
2474 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
2476 MutexAutoLock lock(mQuotaMutex);
2478 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
2479 aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
2480 aOriginMetadata.mGroup);
2482 RefPtr<OriginInfo> originInfo =
2483 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2484 if (!originInfo) {
2485 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
2486 groupInfo, aOriginMetadata.mOrigin, aOriginMetadata.mStorageOrigin,
2487 aOriginMetadata.mIsPrivate, ClientUsageArray(),
2488 /* aUsageBytes */ 0,
2489 /* aAccessTime */ PR_Now(), /* aPersisted */ false,
2490 /* aDirectoryExists */ false));
2494 int64_t QuotaManager::NoteOriginDirectoryCreated(
2495 const OriginMetadata& aOriginMetadata, bool aPersisted) {
2496 AssertIsOnIOThread();
2497 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
2499 int64_t timestamp;
2501 MutexAutoLock lock(mQuotaMutex);
2503 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
2504 aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
2505 aOriginMetadata.mGroup);
2507 RefPtr<OriginInfo> originInfo =
2508 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2509 if (originInfo) {
2510 timestamp = originInfo->LockedAccessTime();
2511 originInfo->mPersisted = aPersisted;
2512 originInfo->mDirectoryExists = true;
2513 } else {
2514 timestamp = PR_Now();
2515 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
2516 groupInfo, aOriginMetadata.mOrigin, aOriginMetadata.mStorageOrigin,
2517 aOriginMetadata.mIsPrivate, ClientUsageArray(),
2518 /* aUsageBytes */ 0,
2519 /* aAccessTime */ timestamp, aPersisted, /* aDirectoryExists */ true));
2522 return timestamp;
2525 void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
2526 int64_t aSize) {
2527 MOZ_ASSERT(!NS_IsMainThread());
2528 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
2530 MutexAutoLock lock(mQuotaMutex);
2532 GroupInfoPair* pair;
2533 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
2534 return;
2537 RefPtr<GroupInfo> groupInfo =
2538 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
2539 if (!groupInfo) {
2540 return;
2543 RefPtr<OriginInfo> originInfo =
2544 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
2545 if (originInfo) {
2546 originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize);
2550 void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) {
2551 MOZ_ASSERT(!NS_IsMainThread());
2552 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
2554 MutexAutoLock lock(mQuotaMutex);
2556 GroupInfoPair* pair;
2557 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
2558 return;
2561 RefPtr<GroupInfo> groupInfo =
2562 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
2563 if (!groupInfo) {
2564 return;
2567 RefPtr<OriginInfo> originInfo =
2568 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
2569 if (originInfo) {
2570 originInfo->LockedResetUsageForClient(aClientMetadata.mClientType);
2574 UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
2575 const OriginMetadata& aOriginMetadata,
2576 Client::Type aClientType) {
2577 MOZ_ASSERT(!NS_IsMainThread());
2578 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
2580 MutexAutoLock lock(mQuotaMutex);
2582 GroupInfoPair* pair;
2583 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
2584 return UsageInfo{};
2587 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2588 if (!groupInfo) {
2589 return UsageInfo{};
2592 RefPtr<OriginInfo> originInfo =
2593 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2594 if (!originInfo) {
2595 return UsageInfo{};
2598 return originInfo->LockedGetUsageForClient(aClientType);
2601 void QuotaManager::UpdateOriginAccessTime(
2602 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
2603 AssertIsOnOwningThread();
2604 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
2605 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
2606 MOZ_ASSERT(!IsShuttingDown());
2608 MutexAutoLock lock(mQuotaMutex);
2610 GroupInfoPair* pair;
2611 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
2612 return;
2615 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2616 if (!groupInfo) {
2617 return;
2620 RefPtr<OriginInfo> originInfo =
2621 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2622 if (originInfo) {
2623 int64_t timestamp = PR_Now();
2624 originInfo->LockedUpdateAccessTime(timestamp);
2626 MutexAutoUnlock autoUnlock(mQuotaMutex);
2628 auto op = CreateSaveOriginAccessTimeOp(WrapMovingNotNullUnchecked(this),
2629 aOriginMetadata, timestamp);
2631 RegisterNormalOriginOp(*op);
2633 op->RunImmediately();
2637 void QuotaManager::RemoveQuota() {
2638 AssertIsOnIOThread();
2640 MutexAutoLock lock(mQuotaMutex);
2642 for (const auto& entry : mGroupInfoPairs) {
2643 const auto& pair = entry.GetData();
2645 MOZ_ASSERT(!entry.GetKey().IsEmpty());
2646 MOZ_ASSERT(pair);
2648 RefPtr<GroupInfo> groupInfo =
2649 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2650 if (groupInfo) {
2651 groupInfo->LockedRemoveOriginInfos();
2654 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
2655 if (groupInfo) {
2656 groupInfo->LockedRemoveOriginInfos();
2659 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
2660 if (groupInfo) {
2661 groupInfo->LockedRemoveOriginInfos();
2665 mGroupInfoPairs.Clear();
2667 MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!");
2670 nsresult QuotaManager::LoadQuota() {
2671 AssertIsOnIOThread();
2672 MOZ_ASSERT(mStorageConnection);
2673 MOZ_ASSERT(!mTemporaryStorageInitializedInternal);
2675 // A list of all unaccessed default or temporary origins.
2676 nsTArray<FullOriginMetadata> unaccessedOrigins;
2678 auto MaybeCollectUnaccessedOrigin =
2679 [loadQuotaInfoStartTime = PR_Now(),
2680 &unaccessedOrigins](auto& fullOriginMetadata) {
2681 if (IsOriginUnaccessed(fullOriginMetadata, loadQuotaInfoStartTime)) {
2682 unaccessedOrigins.AppendElement(std::move(fullOriginMetadata));
2686 auto recordTimeDeltaHelper =
2687 MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0);
2689 const auto startTime = recordTimeDeltaHelper->Start();
2691 auto LoadQuotaFromCache = [&]() -> nsresult {
2692 QM_TRY_INSPECT(
2693 const auto& stmt,
2694 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2695 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
2696 "SELECT repository_id, suffix, group_, "
2697 "origin, client_usages, usage, "
2698 "last_access_time, accessed, persisted "
2699 "FROM origin"_ns));
2701 auto autoRemoveQuota = MakeScopeExit([&] {
2702 RemoveQuota();
2703 unaccessedOrigins.Clear();
2706 QM_TRY(quota::CollectWhileHasResult(
2707 *stmt,
2708 [this,
2709 &MaybeCollectUnaccessedOrigin](auto& stmt) -> Result<Ok, nsresult> {
2710 QM_TRY_INSPECT(const int32_t& repositoryId,
2711 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
2713 const auto maybePersistenceType =
2714 PersistenceTypeFromInt32(repositoryId, fallible);
2715 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
2717 FullOriginMetadata fullOriginMetadata;
2719 fullOriginMetadata.mPersistenceType = maybePersistenceType.value();
2721 QM_TRY_UNWRAP(fullOriginMetadata.mSuffix,
2722 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2723 GetUTF8String, 1));
2725 QM_TRY_UNWRAP(fullOriginMetadata.mGroup,
2726 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2727 GetUTF8String, 2));
2729 QM_TRY_UNWRAP(fullOriginMetadata.mOrigin,
2730 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2731 GetUTF8String, 3));
2733 fullOriginMetadata.mStorageOrigin = fullOriginMetadata.mOrigin;
2735 const auto extraInfo =
2736 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
2737 fullOriginMetadata.mStorageOrigin};
2739 fullOriginMetadata.mIsPrivate = false;
2741 QM_TRY_INSPECT(const auto& clientUsagesText,
2742 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2743 GetUTF8String, 4));
2745 ClientUsageArray clientUsages;
2746 QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText)));
2748 QM_TRY_INSPECT(const int64_t& usage,
2749 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 5));
2750 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
2751 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 6));
2752 QM_TRY_INSPECT(const int64_t& accessed,
2753 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 7));
2754 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
2755 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 8));
2757 QM_TRY_INSPECT(const bool& groupUpdated,
2758 MaybeUpdateGroupForOrigin(fullOriginMetadata));
2760 Unused << groupUpdated;
2762 QM_TRY_INSPECT(
2763 const bool& lastAccessTimeUpdated,
2764 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
2766 Unused << lastAccessTimeUpdated;
2768 // We don't need to update the .metadata-v2 file on disk here,
2769 // EnsureTemporaryOriginIsInitialized is responsible for doing that.
2770 // We just need to use correct group and last access time before
2771 // initializing quota for the given origin. (Note that calling
2772 // LoadFullOriginMetadataWithRestore below might update the group in
2773 // the metadata file, but only as a side-effect. The actual place we
2774 // ensure consistency is in EnsureTemporaryOriginIsInitialized.)
2776 if (accessed) {
2777 QM_TRY_INSPECT(const auto& directory,
2778 GetOriginDirectory(fullOriginMetadata));
2780 QM_TRY_INSPECT(const bool& exists,
2781 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
2783 QM_TRY(OkIf(exists), Err(NS_ERROR_FILE_NOT_FOUND));
2785 QM_TRY_INSPECT(const bool& isDirectory,
2786 MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory));
2788 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FILE_DESTINATION_NOT_DIR));
2790 // Calling LoadFullOriginMetadataWithRestore might update the group
2791 // in the metadata file, but only as a side-effect. The actual place
2792 // we ensure consistency is in EnsureTemporaryOriginIsInitialized.
2794 QM_TRY_INSPECT(const auto& metadata,
2795 LoadFullOriginMetadataWithRestore(directory));
2797 QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
2798 metadata.mLastAccessTime));
2800 QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted),
2801 Err(NS_ERROR_FAILURE));
2803 QM_TRY(OkIf(fullOriginMetadata.mPersistenceType ==
2804 metadata.mPersistenceType),
2805 Err(NS_ERROR_FAILURE));
2807 QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix),
2808 Err(NS_ERROR_FAILURE));
2810 QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup),
2811 Err(NS_ERROR_FAILURE));
2813 QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin),
2814 Err(NS_ERROR_FAILURE));
2816 QM_TRY(OkIf(fullOriginMetadata.mStorageOrigin ==
2817 metadata.mStorageOrigin),
2818 Err(NS_ERROR_FAILURE));
2820 QM_TRY(OkIf(fullOriginMetadata.mIsPrivate == metadata.mIsPrivate),
2821 Err(NS_ERROR_FAILURE));
2823 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
2824 fullOriginMetadata.mPersistenceType, fullOriginMetadata,
2825 fullOriginMetadata.mLastAccessTime,
2826 fullOriginMetadata.mPersisted, directory)));
2827 } else {
2828 InitQuotaForOrigin(fullOriginMetadata, clientUsages, usage);
2831 MaybeCollectUnaccessedOrigin(fullOriginMetadata);
2833 return Ok{};
2834 }));
2836 autoRemoveQuota.release();
2838 return NS_OK;
2841 QM_TRY_INSPECT(
2842 const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> {
2843 if (mCacheUsable) {
2844 QM_TRY_INSPECT(
2845 const auto& stmt,
2846 CreateAndExecuteSingleStepStatement<
2847 SingleStepResult::ReturnNullIfNoResult>(
2848 *mStorageConnection, "SELECT valid, build_id FROM cache"_ns));
2850 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
2852 QM_TRY_INSPECT(const int32_t& valid,
2853 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
2855 if (valid) {
2856 if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) {
2857 return true;
2860 QM_TRY_INSPECT(const auto& buildId,
2861 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2862 nsAutoCString, stmt, GetUTF8String, 1));
2864 return buildId == *gBuildId;
2868 return false;
2869 }()));
2871 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
2873 if (!loadQuotaFromCache ||
2874 !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
2875 ![&LoadQuotaFromCache] {
2876 QM_WARNONLY_TRY_UNWRAP(auto res, MOZ_TO_RESULT(LoadQuotaFromCache()));
2877 return static_cast<bool>(res);
2878 }()) {
2879 // A keeper to defer the return only in Nightly, so that the telemetry data
2880 // for whole profile can be collected.
2881 #ifdef NIGHTLY_BUILD
2882 nsresult statusKeeper = NS_OK;
2883 #endif
2885 const auto statusKeeperFunc = [&](const nsresult rv) {
2886 RECORD_IN_NIGHTLY(statusKeeper, rv);
2889 for (const PersistenceType type :
2890 kInitializableBestEffortPersistenceTypes) {
2891 if (NS_WARN_IF(IsShuttingDown())) {
2892 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
2895 QM_TRY(([&]() -> Result<Ok, nsresult> {
2896 QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] {
2897 const auto innerFunc = [&](const auto&) -> nsresult {
2898 return InitializeRepository(type,
2899 MaybeCollectUnaccessedOrigin);
2902 return ExecuteInitialization(
2903 type == PERSISTENCE_TYPE_DEFAULT
2904 ? Initialization::DefaultRepository
2905 : Initialization::TemporaryRepository,
2906 innerFunc);
2907 }())),
2908 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
2910 return Ok{};
2911 }()));
2914 #ifdef NIGHTLY_BUILD
2915 if (NS_FAILED(statusKeeper)) {
2916 return statusKeeper;
2918 #endif
2921 autoRemoveQuota.release();
2923 const auto endTime = recordTimeDeltaHelper->End();
2925 if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() &&
2926 static_cast<uint32_t>((endTime - startTime).ToMilliseconds()) >=
2927 StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() &&
2928 !unaccessedOrigins.IsEmpty()) {
2929 QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins));
2932 return NS_OK;
2935 void QuotaManager::UnloadQuota() {
2936 AssertIsOnIOThread();
2937 MOZ_ASSERT(mStorageConnection);
2938 MOZ_ASSERT(mTemporaryStorageInitializedInternal);
2939 MOZ_ASSERT(mCacheUsable);
2941 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
2943 mozStorageTransaction transaction(
2944 mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
2946 QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID);
2948 QM_TRY(MOZ_TO_RESULT(
2949 mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)),
2950 QM_VOID);
2952 nsCOMPtr<mozIStorageStatement> insertStmt;
2955 MutexAutoLock lock(mQuotaMutex);
2957 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
2958 MOZ_ASSERT(!iter.Key().IsEmpty());
2960 GroupInfoPair* const pair = iter.UserData();
2961 MOZ_ASSERT(pair);
2963 for (const PersistenceType type : kBestEffortPersistenceTypes) {
2964 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
2965 if (!groupInfo) {
2966 continue;
2969 for (const auto& originInfo : groupInfo->mOriginInfos) {
2970 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count());
2972 if (!originInfo->mDirectoryExists) {
2973 continue;
2976 if (originInfo->mIsPrivate) {
2977 continue;
2980 if (insertStmt) {
2981 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
2982 } else {
2983 QM_TRY_UNWRAP(
2984 insertStmt,
2985 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2986 nsCOMPtr<mozIStorageStatement>, mStorageConnection,
2987 CreateStatement,
2988 "INSERT INTO origin (repository_id, suffix, group_, "
2989 "origin, client_usages, usage, last_access_time, "
2990 "accessed, persisted) "
2991 "VALUES (:repository_id, :suffix, :group_, :origin, "
2992 ":client_usages, :usage, :last_access_time, :accessed, "
2993 ":persisted)"_ns),
2994 QM_VOID);
2997 QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)),
2998 QM_VOID);
3000 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID);
3003 groupInfo->LockedRemoveOriginInfos();
3006 iter.Remove();
3010 QM_TRY_INSPECT(
3011 const auto& stmt,
3012 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3013 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
3014 "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
3015 QM_VOID);
3017 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID);
3018 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)),
3019 QM_VOID);
3020 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
3021 QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
3024 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3025 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
3026 Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
3027 int64_t* aFileSizeOut /* = nullptr */) {
3028 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3029 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
3031 if (aFileSizeOut) {
3032 *aFileSizeOut = 0;
3035 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
3036 return nullptr;
3039 QM_TRY_INSPECT(const auto& path,
3040 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aFile, GetPath),
3041 nullptr);
3043 #ifdef DEBUG
3045 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
3046 nullptr);
3048 nsAutoString clientType;
3049 QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
3050 nullptr);
3052 QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr);
3054 QM_TRY_INSPECT(
3055 const auto& directoryPath,
3056 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directory, GetPath),
3057 nullptr);
3059 MOZ_ASSERT(StringBeginsWith(path, directoryPath));
3061 #endif
3063 QM_TRY_INSPECT(
3064 const int64_t fileSize,
3065 ([&aFile, aFileSize]() -> Result<int64_t, nsresult> {
3066 if (aFileSize == -1) {
3067 QM_TRY_INSPECT(const bool& exists,
3068 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
3070 if (exists) {
3071 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize));
3074 return 0;
3077 return aFileSize;
3078 }()),
3079 nullptr);
3081 RefPtr<QuotaObject> result;
3083 MutexAutoLock lock(mQuotaMutex);
3085 GroupInfoPair* pair;
3086 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
3087 return nullptr;
3090 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3092 if (!groupInfo) {
3093 return nullptr;
3096 RefPtr<OriginInfo> originInfo =
3097 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3099 if (!originInfo) {
3100 return nullptr;
3103 // We need this extra raw pointer because we can't assign to the smart
3104 // pointer directly since QuotaObject::AddRef would try to acquire the same
3105 // mutex.
3106 const NotNull<CanonicalQuotaObject*> canonicalQuotaObject =
3107 originInfo->mCanonicalQuotaObjects.LookupOrInsertWith(path, [&] {
3108 // Create a new QuotaObject. The hashtable is not responsible to
3109 // delete the QuotaObject.
3110 return WrapNotNullUnchecked(new CanonicalQuotaObject(
3111 originInfo, aClientType, path, fileSize));
3114 // Addref the QuotaObject and move the ownership to the result. This must
3115 // happen before we unlock!
3116 result = canonicalQuotaObject->LockedAddRef();
3119 if (aFileSizeOut) {
3120 *aFileSizeOut = fileSize;
3123 // The caller becomes the owner of the QuotaObject, that is, the caller is
3124 // is responsible to delete it when the last reference is removed.
3125 return result.forget();
3128 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3129 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
3130 Client::Type aClientType, const nsAString& aPath, int64_t aFileSize,
3131 int64_t* aFileSizeOut /* = nullptr */) {
3132 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3134 if (aFileSizeOut) {
3135 *aFileSizeOut = 0;
3138 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr);
3140 return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file,
3141 aFileSize, aFileSizeOut);
3144 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3145 const int64_t aDirectoryLockId, const nsAString& aPath) {
3146 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3148 Maybe<MutexAutoLock> lock;
3150 // See the comment for mDirectoryLockIdTable in QuotaManager.h
3151 if (!IsOnBackgroundThread()) {
3152 lock.emplace(mQuotaMutex);
3155 if (auto maybeDirectoryLock =
3156 mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) {
3157 const auto& directoryLock = *maybeDirectoryLock;
3158 MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable());
3160 const PersistenceType persistenceType = directoryLock->GetPersistenceType();
3161 const OriginMetadata& originMetadata = directoryLock->OriginMetadata();
3162 const Client::Type clientType = directoryLock->ClientType();
3164 lock.reset();
3166 return GetQuotaObject(persistenceType, originMetadata, clientType, aPath);
3169 MOZ_ASSERT(aDirectoryLockId == -1);
3170 return nullptr;
3173 Nullable<bool> QuotaManager::OriginPersisted(
3174 const OriginMetadata& aOriginMetadata) {
3175 AssertIsOnIOThread();
3177 MutexAutoLock lock(mQuotaMutex);
3179 RefPtr<OriginInfo> originInfo =
3180 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
3181 if (originInfo) {
3182 return Nullable<bool>(originInfo->LockedPersisted());
3185 return Nullable<bool>();
3188 void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) {
3189 AssertIsOnIOThread();
3191 MutexAutoLock lock(mQuotaMutex);
3193 RefPtr<OriginInfo> originInfo =
3194 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
3195 if (originInfo && !originInfo->LockedPersisted()) {
3196 originInfo->LockedPersist();
3200 void QuotaManager::AbortOperationsForLocks(
3201 const DirectoryLockIdTableArray& aLockIds) {
3202 for (Client::Type type : AllClientTypes()) {
3203 if (aLockIds[type].Filled()) {
3204 (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]);
3209 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
3210 AssertIsOnOwningThread();
3212 for (const RefPtr<Client>& client : *mClients) {
3213 client->AbortOperationsForProcess(aContentParentId);
3217 Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetOriginDirectory(
3218 const OriginMetadata& aOriginMetadata) const {
3219 QM_TRY_UNWRAP(
3220 auto directory,
3221 QM_NewLocalFile(GetStoragePath(aOriginMetadata.mPersistenceType)));
3223 QM_TRY(MOZ_TO_RESULT(directory->Append(
3224 MakeSanitizedOriginString(aOriginMetadata.mStorageOrigin))));
3226 return directory;
3229 Result<bool, nsresult> QuotaManager::DoesOriginDirectoryExist(
3230 const OriginMetadata& aOriginMetadata) const {
3231 AssertIsOnIOThread();
3233 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata));
3235 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
3238 // static
3239 nsresult QuotaManager::CreateDirectoryMetadata(
3240 nsIFile& aDirectory, int64_t aTimestamp,
3241 const OriginMetadata& aOriginMetadata) {
3242 AssertIsOnIOThread();
3244 StorageOriginAttributes groupAttributes;
3246 nsCString groupNoSuffix;
3247 QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
3248 groupNoSuffix)),
3249 NS_ERROR_FAILURE);
3251 nsCString groupPrefix;
3252 GetJarPrefix(groupAttributes.InIsolatedMozBrowser(), groupPrefix);
3254 nsCString group = groupPrefix + groupNoSuffix;
3256 StorageOriginAttributes originAttributes;
3258 nsCString originNoSuffix;
3259 QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
3260 originNoSuffix)),
3261 NS_ERROR_FAILURE);
3263 nsCString originPrefix;
3264 GetJarPrefix(originAttributes.InIsolatedMozBrowser(), originPrefix);
3266 nsCString origin = originPrefix + originNoSuffix;
3268 MOZ_ASSERT(groupPrefix == originPrefix);
3270 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3271 nsCOMPtr<nsIFile>, aDirectory, Clone));
3273 QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME))));
3275 QM_TRY_INSPECT(const auto& stream,
3276 GetBinaryOutputStream(*file, FileFlag::Truncate));
3277 MOZ_ASSERT(stream);
3279 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
3281 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(group.get())));
3283 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(origin.get())));
3285 // Currently unused (used to be isApp).
3286 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
3288 QM_TRY(MOZ_TO_RESULT(stream->Flush()));
3290 QM_TRY(MOZ_TO_RESULT(stream->Close()));
3292 QM_TRY(MOZ_TO_RESULT(
3293 file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME))));
3295 return NS_OK;
3298 // static
3299 nsresult QuotaManager::CreateDirectoryMetadata2(
3300 nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted,
3301 const OriginMetadata& aOriginMetadata) {
3302 AssertIsOnIOThread();
3304 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3305 nsCOMPtr<nsIFile>, aDirectory, Clone));
3307 QM_TRY(
3308 MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME))));
3310 QM_TRY_INSPECT(const auto& stream,
3311 GetBinaryOutputStream(*file, FileFlag::Truncate));
3312 MOZ_ASSERT(stream);
3314 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
3316 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aPersisted)));
3318 // Reserved data 1
3319 QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
3321 // Reserved data 2
3322 QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
3324 // Currently unused (used to be suffix).
3325 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
3327 // Currently unused (used to be group).
3328 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
3330 QM_TRY(MOZ_TO_RESULT(
3331 stream->WriteStringZ(aOriginMetadata.mStorageOrigin.get())));
3333 // Currently used for isPrivate (used to be used for isApp).
3334 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aOriginMetadata.mIsPrivate)));
3336 QM_TRY(MOZ_TO_RESULT(stream->Flush()));
3338 QM_TRY(MOZ_TO_RESULT(stream->Close()));
3340 QM_TRY(MOZ_TO_RESULT(
3341 file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME))));
3343 return NS_OK;
3346 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) {
3347 AssertIsOnIOThread();
3348 MOZ_ASSERT(aDirectory);
3349 MOZ_ASSERT(mStorageConnection);
3351 RefPtr<RestoreDirectoryMetadata2Helper> helper =
3352 new RestoreDirectoryMetadata2Helper(aDirectory);
3354 QM_TRY(MOZ_TO_RESULT(helper->Init()));
3356 QM_TRY(MOZ_TO_RESULT(helper->RestoreMetadata2File()));
3358 return NS_OK;
3361 Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata(
3362 nsIFile* aDirectory, PersistenceType aPersistenceType) {
3363 MOZ_ASSERT(!NS_IsMainThread());
3364 MOZ_ASSERT(aDirectory);
3365 MOZ_ASSERT(mStorageConnection);
3367 QM_TRY_INSPECT(const auto& binaryStream,
3368 GetBinaryInputStream(*aDirectory,
3369 nsLiteralString(METADATA_V2_FILE_NAME)));
3371 FullOriginMetadata fullOriginMetadata;
3373 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
3374 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
3376 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
3377 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
3379 QM_TRY_INSPECT(const bool& reservedData1,
3380 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
3381 Unused << reservedData1;
3383 // XXX Use for the persistence type.
3384 QM_TRY_INSPECT(const bool& reservedData2,
3385 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
3386 Unused << reservedData2;
3388 fullOriginMetadata.mPersistenceType = aPersistenceType;
3390 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3391 nsCString, binaryStream, ReadCString));
3392 Unused << suffix;
3394 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3395 nsCString, binaryStream, ReadCString));
3396 Unused << group;
3398 QM_TRY_UNWRAP(
3399 fullOriginMetadata.mStorageOrigin,
3400 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
3402 // Currently used for isPrivate (used to be used for isApp).
3403 QM_TRY_UNWRAP(fullOriginMetadata.mIsPrivate,
3404 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
3406 QM_TRY(MOZ_TO_RESULT(binaryStream->Close()));
3408 auto principal =
3409 [&storageOrigin =
3410 fullOriginMetadata.mStorageOrigin]() -> nsCOMPtr<nsIPrincipal> {
3411 if (storageOrigin.EqualsLiteral(kChromeOrigin)) {
3412 return SystemPrincipal::Get();
3414 return BasePrincipal::CreateContentPrincipal(storageOrigin);
3415 }();
3416 QM_TRY(MOZ_TO_RESULT(principal));
3418 PrincipalInfo principalInfo;
3419 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
3421 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
3422 Err(NS_ERROR_MALFORMED_URI));
3424 QM_TRY_UNWRAP(auto principalMetadata,
3425 GetInfoFromValidatedPrincipalInfo(principalInfo));
3427 fullOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix);
3428 fullOriginMetadata.mGroup = std::move(principalMetadata.mGroup);
3429 fullOriginMetadata.mOrigin = std::move(principalMetadata.mOrigin);
3431 QM_TRY_INSPECT(const bool& groupUpdated,
3432 MaybeUpdateGroupForOrigin(fullOriginMetadata));
3434 // A workaround for a bug in GetLastModifiedTime implementation which should
3435 // have returned the current time instead of INT64_MIN when there were no
3436 // suitable files for getting last modified time.
3437 QM_TRY_INSPECT(const bool& lastAccessTimeUpdated,
3438 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
3440 if (groupUpdated || lastAccessTimeUpdated) {
3441 // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
3442 // I/O.
3443 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
3444 *aDirectory, fullOriginMetadata.mLastAccessTime,
3445 fullOriginMetadata.mPersisted, fullOriginMetadata)));
3448 return fullOriginMetadata;
3451 Result<FullOriginMetadata, nsresult>
3452 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) {
3453 // XXX Once the persistence type is stored in the metadata file, this block
3454 // for getting the persistence type from the parent directory name can be
3455 // removed.
3456 nsCOMPtr<nsIFile> parentDir;
3457 QM_TRY(MOZ_TO_RESULT(aDirectory->GetParent(getter_AddRefs(parentDir))));
3459 const auto maybePersistenceType =
3460 PersistenceTypeFromFile(*parentDir, fallible);
3461 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
3463 const auto& persistenceType = maybePersistenceType.value();
3465 QM_TRY_RETURN(QM_OR_ELSE_WARN(
3466 // Expression.
3467 LoadFullOriginMetadata(aDirectory, persistenceType),
3468 // Fallback.
3469 ([&aDirectory, &persistenceType,
3470 this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> {
3471 QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory)));
3473 QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType));
3474 })));
3477 Result<OriginMetadata, nsresult> QuotaManager::GetOriginMetadata(
3478 nsIFile* aDirectory) {
3479 MOZ_ASSERT(aDirectory);
3480 MOZ_ASSERT(mStorageConnection);
3482 QM_TRY_INSPECT(
3483 const auto& leafName,
3484 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aDirectory, GetLeafName));
3486 // XXX Consider using QuotaManager::ParseOrigin here.
3487 nsCString spec;
3488 OriginAttributes attrs;
3489 nsCString originalSuffix;
3490 OriginParser::ResultType result = OriginParser::ParseOrigin(
3491 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
3492 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
3494 QM_TRY_INSPECT(
3495 const auto& principal,
3496 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
3497 if (spec.EqualsLiteral(kChromeOrigin)) {
3498 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
3501 nsCOMPtr<nsIURI> uri;
3502 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
3504 return nsCOMPtr<nsIPrincipal>(
3505 BasePrincipal::CreateContentPrincipal(uri, attrs));
3506 }()));
3507 QM_TRY(MOZ_TO_RESULT(principal));
3509 PrincipalInfo principalInfo;
3510 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
3512 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
3513 Err(NS_ERROR_MALFORMED_URI));
3515 QM_TRY_UNWRAP(auto principalMetadata,
3516 GetInfoFromValidatedPrincipalInfo(principalInfo));
3518 QM_TRY_INSPECT(const auto& parentDirectory,
3519 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<nsIFile>,
3520 aDirectory, GetParent));
3522 const auto maybePersistenceType =
3523 PersistenceTypeFromFile(*parentDirectory, fallible);
3524 QM_TRY(MOZ_TO_RESULT(maybePersistenceType.isSome()));
3526 return OriginMetadata{std::move(principalMetadata),
3527 maybePersistenceType.value()};
3530 Result<Ok, nsresult> QuotaManager::RemoveOriginDirectory(nsIFile& aDirectory) {
3531 AssertIsOnIOThread();
3533 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) {
3534 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.Remove(true)));
3537 QM_TRY_INSPECT(const auto& toBeRemovedStorageDir,
3538 QM_NewLocalFile(*mToBeRemovedStoragePath));
3540 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*toBeRemovedStorageDir));
3542 (void)created;
3544 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.MoveTo(
3545 toBeRemovedStorageDir, NSID_TrimBracketsUTF16(nsID::GenerateUUID()))));
3548 Result<bool, nsresult> QuotaManager::DoesClientDirectoryExist(
3549 const ClientMetadata& aClientMetadata) const {
3550 AssertIsOnIOThread();
3552 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aClientMetadata));
3554 QM_TRY(MOZ_TO_RESULT(
3555 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
3557 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
3560 template <typename OriginFunc>
3561 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType,
3562 OriginFunc&& aOriginFunc) {
3563 AssertIsOnIOThread();
3564 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
3565 aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
3567 QM_TRY_INSPECT(const auto& directory,
3568 QM_NewLocalFile(GetStoragePath(aPersistenceType)));
3570 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory));
3572 Unused << created;
3574 // A keeper to defer the return only in Nightly, so that the telemetry data
3575 // for whole profile can be collected
3576 #ifdef NIGHTLY_BUILD
3577 nsresult statusKeeper = NS_OK;
3578 #endif
3580 const auto statusKeeperFunc = [&](const nsresult rv) {
3581 RECORD_IN_NIGHTLY(statusKeeper, rv);
3584 struct RenameAndInitInfo {
3585 nsCOMPtr<nsIFile> mOriginDirectory;
3586 FullOriginMetadata mFullOriginMetadata;
3587 int64_t mTimestamp;
3588 bool mPersisted;
3590 nsTArray<RenameAndInitInfo> renameAndInitInfos;
3592 QM_TRY(([&]() -> Result<Ok, nsresult> {
3593 QM_TRY(
3594 CollectEachFile(
3595 *directory,
3596 [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> {
3597 if (NS_WARN_IF(IsShuttingDown())) {
3598 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
3601 QM_TRY(
3602 ([this, &childDirectory, &renameAndInitInfos,
3603 aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> {
3604 QM_TRY_INSPECT(
3605 const auto& leafName,
3606 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3607 nsAutoString, childDirectory, GetLeafName));
3609 QM_TRY_INSPECT(const auto& dirEntryKind,
3610 GetDirEntryKind(*childDirectory));
3612 switch (dirEntryKind) {
3613 case nsIFileKind::ExistsAsDirectory: {
3614 QM_TRY_UNWRAP(
3615 auto maybeMetadata,
3616 QM_OR_ELSE_WARN_IF(
3617 // Expression
3618 LoadFullOriginMetadataWithRestore(
3619 childDirectory)
3620 .map([](auto metadata)
3621 -> Maybe<FullOriginMetadata> {
3622 return Some(std::move(metadata));
3624 // Predicate.
3625 IsSpecificError<NS_ERROR_MALFORMED_URI>,
3626 // Fallback.
3627 ErrToDefaultOk<Maybe<FullOriginMetadata>>));
3629 if (!maybeMetadata) {
3630 // Unknown directories during initialization are
3631 // allowed. Just warn if we find them.
3632 UNKNOWN_FILE_WARNING(leafName);
3633 break;
3636 auto metadata = maybeMetadata.extract();
3638 MOZ_ASSERT(metadata.mPersistenceType ==
3639 aPersistenceType);
3641 const auto extraInfo = ScopedLogExtraInfo{
3642 ScopedLogExtraInfo::kTagStorageOriginTainted,
3643 metadata.mStorageOrigin};
3645 // FIXME(tt): The check for origin name consistency can
3646 // be removed once we have an upgrade to traverse origin
3647 // directories and check through the directory metadata
3648 // files.
3649 const auto originSanitized =
3650 MakeSanitizedOriginCString(metadata.mOrigin);
3652 NS_ConvertUTF16toUTF8 utf8LeafName(leafName);
3653 if (!originSanitized.Equals(utf8LeafName)) {
3654 QM_WARNING(
3655 "The name of the origin directory (%s) doesn't "
3656 "match the sanitized origin string (%s) in the "
3657 "metadata file!",
3658 utf8LeafName.get(), originSanitized.get());
3660 // If it's the known case, we try to restore the
3661 // origin directory name if it's possible.
3662 if (originSanitized.Equals(utf8LeafName + "."_ns)) {
3663 const int64_t lastAccessTime =
3664 metadata.mLastAccessTime;
3665 const bool persisted = metadata.mPersisted;
3666 renameAndInitInfos.AppendElement(RenameAndInitInfo{
3667 std::move(childDirectory), std::move(metadata),
3668 lastAccessTime, persisted});
3669 break;
3672 // XXXtt: Try to restore the unknown cases base on the
3673 // content for their metadata files. Note that if the
3674 // restore fails, QM should maintain a list and ensure
3675 // they won't be accessed after initialization.
3678 QM_TRY(QM_OR_ELSE_WARN_IF(
3679 // Expression.
3680 MOZ_TO_RESULT(InitializeOrigin(
3681 aPersistenceType, metadata,
3682 metadata.mLastAccessTime, metadata.mPersisted,
3683 childDirectory)),
3684 // Predicate.
3685 IsDatabaseCorruptionError,
3686 // Fallback.
3687 ([&childDirectory](
3688 const nsresult rv) -> Result<Ok, nsresult> {
3689 // If the origin can't be initialized due to
3690 // corruption, this is a permanent
3691 // condition, and we need to remove all data
3692 // for the origin on disk.
3694 QM_TRY(
3695 MOZ_TO_RESULT(childDirectory->Remove(true)));
3697 return Ok{};
3698 })));
3700 std::forward<OriginFunc>(aOriginFunc)(metadata);
3702 break;
3705 case nsIFileKind::ExistsAsFile:
3706 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
3707 break;
3710 // Unknown files during initialization are now allowed.
3711 // Just warn if we find them.
3712 UNKNOWN_FILE_WARNING(leafName);
3713 break;
3715 case nsIFileKind::DoesNotExist:
3716 // Ignore files that got removed externally while
3717 // iterating.
3718 break;
3721 return Ok{};
3722 }()),
3723 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3725 return Ok{};
3727 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3729 return Ok{};
3730 }()));
3732 for (auto& info : renameAndInitInfos) {
3733 QM_TRY(([&]() -> Result<Ok, nsresult> {
3734 QM_TRY(([&directory, &info, this, aPersistenceType,
3735 &aOriginFunc]() -> Result<Ok, nsresult> {
3736 const auto extraInfo = ScopedLogExtraInfo{
3737 ScopedLogExtraInfo::kTagStorageOriginTainted,
3738 info.mFullOriginMetadata.mStorageOrigin};
3740 const auto originDirName =
3741 MakeSanitizedOriginString(info.mFullOriginMetadata.mOrigin);
3743 // Check if targetDirectory exist.
3744 QM_TRY_INSPECT(const auto& targetDirectory,
3745 CloneFileAndAppend(*directory, originDirName));
3747 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(
3748 targetDirectory, Exists));
3750 if (exists) {
3751 QM_TRY(MOZ_TO_RESULT(info.mOriginDirectory->Remove(true)));
3753 return Ok{};
3756 QM_TRY(MOZ_TO_RESULT(
3757 info.mOriginDirectory->RenameTo(nullptr, originDirName)));
3759 // XXX We don't check corruption here ?
3760 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
3761 aPersistenceType, info.mFullOriginMetadata, info.mTimestamp,
3762 info.mPersisted, targetDirectory)));
3764 std::forward<OriginFunc>(aOriginFunc)(info.mFullOriginMetadata);
3766 return Ok{};
3767 }()),
3768 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3770 return Ok{};
3771 }()));
3774 #ifdef NIGHTLY_BUILD
3775 if (NS_FAILED(statusKeeper)) {
3776 return statusKeeper;
3778 #endif
3780 return NS_OK;
3783 nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
3784 const OriginMetadata& aOriginMetadata,
3785 int64_t aAccessTime, bool aPersisted,
3786 nsIFile* aDirectory) {
3787 AssertIsOnIOThread();
3789 // The ScopedLogExtraInfo is not set here on purpose, so the callers can
3790 // decide if they want to set it. The extra info can be set sooner this way
3791 // as well.
3793 const bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
3795 // We need to initialize directories of all clients if they exists and also
3796 // get the total usage to initialize the quota.
3798 ClientUsageArray clientUsages;
3800 // A keeper to defer the return only in Nightly, so that the telemetry data
3801 // for whole profile can be collected
3802 #ifdef NIGHTLY_BUILD
3803 nsresult statusKeeper = NS_OK;
3804 #endif
3806 QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) {
3807 RECORD_IN_NIGHTLY(statusKeeper, rv);
3808 }]() -> Result<Ok, nsresult> {
3809 QM_TRY(
3810 CollectEachFile(
3811 *aDirectory,
3812 [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
3813 if (NS_WARN_IF(IsShuttingDown())) {
3814 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
3817 QM_TRY(
3818 ([this, &file, trackQuota, aPersistenceType, &aOriginMetadata,
3819 &clientUsages]() -> Result<Ok, nsresult> {
3820 QM_TRY_INSPECT(const auto& leafName,
3821 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3822 nsAutoString, file, GetLeafName));
3824 QM_TRY_INSPECT(const auto& dirEntryKind,
3825 GetDirEntryKind(*file));
3827 switch (dirEntryKind) {
3828 case nsIFileKind::ExistsAsDirectory: {
3829 Client::Type clientType;
3830 const bool ok = Client::TypeFromText(
3831 leafName, clientType, fallible);
3832 if (!ok) {
3833 // Unknown directories during initialization are now
3834 // allowed. Just warn if we find them.
3835 UNKNOWN_FILE_WARNING(leafName);
3836 break;
3839 if (trackQuota) {
3840 QM_TRY_INSPECT(
3841 const auto& usageInfo,
3842 (*mClients)[clientType]->InitOrigin(
3843 aPersistenceType, aOriginMetadata,
3844 /* aCanceled */ Atomic<bool>(false)));
3846 MOZ_ASSERT(!clientUsages[clientType]);
3848 if (usageInfo.TotalUsage()) {
3849 // XXX(Bug 1683863) Until we identify the root cause
3850 // of seemingly converted-from-negative usage
3851 // values, we will just treat them as unset here,
3852 // but log a warning to the browser console.
3853 if (static_cast<int64_t>(*usageInfo.TotalUsage()) >=
3854 0) {
3855 clientUsages[clientType] = usageInfo.TotalUsage();
3856 } else {
3857 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
3858 const nsCOMPtr<nsIConsoleService> console =
3859 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
3860 if (console) {
3861 console->LogStringMessage(
3862 nsString(
3863 u"QuotaManager warning: client "_ns +
3864 leafName +
3865 u" reported negative usage for group "_ns +
3866 NS_ConvertUTF8toUTF16(
3867 aOriginMetadata.mGroup) +
3868 u", origin "_ns +
3869 NS_ConvertUTF8toUTF16(
3870 aOriginMetadata.mOrigin))
3871 .get());
3873 #endif
3876 } else {
3877 QM_TRY(MOZ_TO_RESULT(
3878 (*mClients)[clientType]
3879 ->InitOriginWithoutTracking(
3880 aPersistenceType, aOriginMetadata,
3881 /* aCanceled */ Atomic<bool>(false))));
3884 break;
3887 case nsIFileKind::ExistsAsFile:
3888 if (IsOriginMetadata(leafName)) {
3889 break;
3892 if (IsTempMetadata(leafName)) {
3893 QM_TRY(MOZ_TO_RESULT(
3894 file->Remove(/* recursive */ false)));
3896 break;
3899 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
3900 break;
3903 // Unknown files during initialization are now allowed.
3904 // Just warn if we find them.
3905 UNKNOWN_FILE_WARNING(leafName);
3906 // Bug 1595448 will handle the case for unknown files
3907 // like idb, cache, or ls.
3908 break;
3910 case nsIFileKind::DoesNotExist:
3911 // Ignore files that got removed externally while
3912 // iterating.
3913 break;
3916 return Ok{};
3917 }()),
3918 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3920 return Ok{};
3922 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3924 return Ok{};
3925 }()));
3927 #ifdef NIGHTLY_BUILD
3928 if (NS_FAILED(statusKeeper)) {
3929 return statusKeeper;
3931 #endif
3933 if (trackQuota) {
3934 const auto usage = std::accumulate(
3935 clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0),
3936 [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) {
3937 return value + clientUsage.valueOr(0);
3940 // XXX Should we log more information, i.e. the whole clientUsages array, in
3941 // case usage is not valid?
3943 QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE);
3945 InitQuotaForOrigin(
3946 FullOriginMetadata{aOriginMetadata, aPersisted, aAccessTime},
3947 clientUsages, usage.value());
3950 return NS_OK;
3953 nsresult
3954 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
3955 nsIFile* aIndexedDBDir) {
3956 AssertIsOnIOThread();
3957 MOZ_ASSERT(aIndexedDBDir);
3959 const auto innerFunc = [this, &aIndexedDBDir](const auto&) -> nsresult {
3960 bool isDirectory;
3961 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->IsDirectory(&isDirectory)));
3963 if (!isDirectory) {
3964 NS_WARNING("indexedDB entry is not a directory!");
3965 return NS_OK;
3968 auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath);
3969 if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) {
3970 return persistentStorageDirOrErr.unwrapErr();
3973 nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap();
3975 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
3976 nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
3978 bool exists;
3979 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Exists(&exists)));
3981 if (exists) {
3982 QM_WARNING("Deleting old <profile>/indexedDB directory!");
3984 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->Remove(/* aRecursive */ true)));
3986 return NS_OK;
3989 nsCOMPtr<nsIFile> storageDir;
3990 QM_TRY(MOZ_TO_RESULT(
3991 persistentStorageDir->GetParent(getter_AddRefs(storageDir))));
3993 // MoveTo() is atomic if the move happens on the same volume which should
3994 // be our case, so even if we crash in the middle of the operation nothing
3995 // breaks next time we try to initialize.
3996 // However there's a theoretical possibility that the indexedDB directory
3997 // is on different volume, but it should be rare enough that we don't have
3998 // to worry about it.
3999 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->MoveTo(
4000 storageDir, nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
4002 return NS_OK;
4005 return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory,
4006 innerFunc);
4009 nsresult
4010 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
4011 nsIFile* aPersistentStorageDir) {
4012 AssertIsOnIOThread();
4013 MOZ_ASSERT(aPersistentStorageDir);
4015 const auto innerFunc = [this,
4016 &aPersistentStorageDir](const auto&) -> nsresult {
4017 QM_TRY_INSPECT(
4018 const bool& isDirectory,
4019 MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir, IsDirectory));
4021 if (!isDirectory) {
4022 NS_WARNING("persistent entry is not a directory!");
4023 return NS_OK;
4027 QM_TRY_INSPECT(const auto& defaultStorageDir,
4028 QM_NewLocalFile(*mDefaultStoragePath));
4030 QM_TRY_INSPECT(const bool& exists,
4031 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
4033 if (exists) {
4034 QM_WARNING("Deleting old <profile>/storage/persistent directory!");
4036 QM_TRY(MOZ_TO_RESULT(
4037 aPersistentStorageDir->Remove(/* aRecursive */ true)));
4039 return NS_OK;
4044 // Create real metadata files for origin directories in persistent
4045 // storage.
4046 auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
4047 aPersistentStorageDir);
4049 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4051 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4053 // Upgrade metadata files for origin directories in temporary storage.
4054 QM_TRY_INSPECT(const auto& temporaryStorageDir,
4055 QM_NewLocalFile(*mTemporaryStoragePath));
4057 QM_TRY_INSPECT(const bool& exists,
4058 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, Exists));
4060 if (exists) {
4061 QM_TRY_INSPECT(
4062 const bool& isDirectory,
4063 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, IsDirectory));
4065 if (!isDirectory) {
4066 NS_WARNING("temporary entry is not a directory!");
4067 return NS_OK;
4070 helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
4071 temporaryStorageDir);
4073 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4075 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4079 // And finally rename persistent to default.
4080 QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir->RenameTo(
4081 nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME))));
4083 return NS_OK;
4086 return ExecuteInitialization(
4087 Initialization::UpgradeFromPersistentStorageDirectory, innerFunc);
4090 template <typename Helper>
4091 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
4092 const int32_t aNewVersion,
4093 mozIStorageConnection* aConnection) {
4094 AssertIsOnIOThread();
4095 MOZ_ASSERT(aNewVersion > aOldVersion);
4096 MOZ_ASSERT(aNewVersion <= kStorageVersion);
4097 MOZ_ASSERT(aConnection);
4099 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
4100 QM_TRY_UNWRAP(auto directory,
4101 QM_NewLocalFile(GetStoragePath(persistenceType)));
4103 QM_TRY_INSPECT(const bool& exists,
4104 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
4106 if (!exists) {
4107 continue;
4110 RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory);
4112 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4114 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4117 #ifdef DEBUG
4119 QM_TRY_INSPECT(const int32_t& storageVersion,
4120 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4122 MOZ_ASSERT(storageVersion == aOldVersion);
4124 #endif
4126 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(aNewVersion)));
4128 return NS_OK;
4131 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
4132 mozIStorageConnection* aConnection) {
4133 AssertIsOnIOThread();
4134 MOZ_ASSERT(aConnection);
4136 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4137 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
4138 0, MakeStorageVersion(1, 0), aConnection)));
4140 return NS_OK;
4143 return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0,
4144 innerFunc);
4147 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
4148 mozIStorageConnection* aConnection) {
4149 AssertIsOnIOThread();
4150 MOZ_ASSERT(aConnection);
4152 // The upgrade consists of a number of logically distinct bugs that
4153 // intentionally got fixed at the same time to trigger just one major
4154 // version bump.
4157 // Morgue directory cleanup
4158 // [Feature/Bug]:
4159 // The original bug that added "on demand" morgue cleanup is 1165119.
4161 // [Mutations]:
4162 // Morgue directories are removed from all origin directories during the
4163 // upgrade process. Origin initialization and usage calculation doesn't try
4164 // to remove morgue directories anymore.
4166 // [Downgrade-incompatible changes]:
4167 // Morgue directories can reappear if user runs an already upgraded profile
4168 // in an older version of Firefox. Morgue directories then prevent current
4169 // Firefox from initializing and using the storage.
4172 // App data removal
4173 // [Feature/Bug]:
4174 // The bug that removes isApp flags is 1311057.
4176 // [Mutations]:
4177 // Origin directories with appIds are removed during the upgrade process.
4179 // [Downgrade-incompatible changes]:
4180 // Origin directories with appIds can reappear if user runs an already
4181 // upgraded profile in an older version of Firefox. Origin directories with
4182 // appIds don't prevent current Firefox from initializing and using the
4183 // storage, but they wouldn't ever be removed again, potentially causing
4184 // problems once appId is removed from origin attributes.
4187 // Strip obsolete origin attributes
4188 // [Feature/Bug]:
4189 // The bug that strips obsolete origin attributes is 1314361.
4191 // [Mutations]:
4192 // Origin directories with obsolete origin attributes are renamed and their
4193 // metadata files are updated during the upgrade process.
4195 // [Downgrade-incompatible changes]:
4196 // Origin directories with obsolete origin attributes can reappear if user
4197 // runs an already upgraded profile in an older version of Firefox. Origin
4198 // directories with obsolete origin attributes don't prevent current Firefox
4199 // from initializing and using the storage, but they wouldn't ever be upgraded
4200 // again, potentially causing problems in future.
4203 // File manager directory renaming (client specific)
4204 // [Feature/Bug]:
4205 // The original bug that added "on demand" file manager directory renaming is
4206 // 1056939.
4208 // [Mutations]:
4209 // All file manager directories are renamed to contain the ".files" suffix.
4211 // [Downgrade-incompatible changes]:
4212 // File manager directories with the ".files" suffix prevent older versions of
4213 // Firefox from initializing and using the storage.
4214 // File manager directories without the ".files" suffix can appear if user
4215 // runs an already upgraded profile in an older version of Firefox. File
4216 // manager directories without the ".files" suffix then prevent current
4217 // Firefox from initializing and using the storage.
4219 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4220 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
4221 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection)));
4223 return NS_OK;
4226 return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0,
4227 innerFunc);
4230 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
4231 mozIStorageConnection* aConnection) {
4232 AssertIsOnIOThread();
4233 MOZ_ASSERT(aConnection);
4235 // The upgrade is mainly to create a directory padding file in DOM Cache
4236 // directory to record the overall padding size of an origin.
4238 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4239 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
4240 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection)));
4242 return NS_OK;
4245 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1,
4246 innerFunc);
4249 nsresult QuotaManager::UpgradeStorageFrom2_1To2_2(
4250 mozIStorageConnection* aConnection) {
4251 AssertIsOnIOThread();
4252 MOZ_ASSERT(aConnection);
4254 // The upgrade is mainly to clean obsolete origins in the repositoies, remove
4255 // asmjs client, and ".tmp" file in the idb folers.
4257 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4258 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>(
4259 MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection)));
4261 return NS_OK;
4264 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2,
4265 innerFunc);
4268 nsresult QuotaManager::UpgradeStorageFrom2_2To2_3(
4269 mozIStorageConnection* aConnection) {
4270 AssertIsOnIOThread();
4271 MOZ_ASSERT(aConnection);
4273 const auto innerFunc = [&aConnection](const auto&) -> nsresult {
4274 // Table `database`
4275 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
4276 nsLiteralCString("CREATE TABLE database"
4277 "( cache_version INTEGER NOT NULL DEFAULT 0"
4278 ");"))));
4280 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
4281 nsLiteralCString("INSERT INTO database (cache_version) "
4282 "VALUES (0)"))));
4284 #ifdef DEBUG
4286 QM_TRY_INSPECT(
4287 const int32_t& storageVersion,
4288 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4290 MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2));
4292 #endif
4294 QM_TRY(
4295 MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3))));
4297 return NS_OK;
4300 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3,
4301 innerFunc);
4304 nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
4305 nsIFile& aLsArchiveFile) {
4306 AssertIsOnIOThread();
4307 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
4309 QM_TRY_INSPECT(const bool& exists,
4310 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4312 if (!exists) {
4313 // If the ls archive doesn't exist then ls directories can't exist either.
4314 return NS_OK;
4317 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4319 InvalidateQuotaCache();
4321 // Finally remove the ls archive, so we don't have to check all origin
4322 // directories next time this method is called.
4323 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
4325 return NS_OK;
4328 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
4329 AssertIsOnIOThread();
4331 QM_TRY_INSPECT(const auto& defaultStorageDir,
4332 QM_NewLocalFile(*mDefaultStoragePath));
4334 QM_TRY_INSPECT(const bool& exists,
4335 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
4337 if (!exists) {
4338 return NS_OK;
4341 QM_TRY(CollectEachFile(
4342 *defaultStorageDir,
4343 [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
4344 #ifdef DEBUG
4346 QM_TRY_INSPECT(const bool& exists,
4347 MOZ_TO_RESULT_INVOKE_MEMBER(originDir, Exists));
4348 MOZ_ASSERT(exists);
4350 #endif
4352 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
4354 switch (dirEntryKind) {
4355 case nsIFileKind::ExistsAsDirectory: {
4356 QM_TRY_INSPECT(
4357 const auto& lsDir,
4358 CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING(
4359 LS_DIRECTORY_NAME)));
4362 QM_TRY_INSPECT(const bool& exists,
4363 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, Exists));
4365 if (!exists) {
4366 return Ok{};
4371 QM_TRY_INSPECT(const bool& isDirectory,
4372 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, IsDirectory));
4374 if (!isDirectory) {
4375 QM_WARNING("ls entry is not a directory!");
4377 return Ok{};
4381 nsString path;
4382 QM_TRY(MOZ_TO_RESULT(lsDir->GetPath(path)));
4384 QM_WARNING("Deleting %s directory!",
4385 NS_ConvertUTF16toUTF8(path).get());
4387 QM_TRY(MOZ_TO_RESULT(lsDir->Remove(/* aRecursive */ true)));
4389 break;
4392 case nsIFileKind::ExistsAsFile: {
4393 QM_TRY_INSPECT(const auto& leafName,
4394 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4395 nsAutoString, originDir, GetLeafName));
4397 // Unknown files during upgrade are allowed. Just warn if we find
4398 // them.
4399 if (!IsOSMetadata(leafName)) {
4400 UNKNOWN_FILE_WARNING(leafName);
4403 break;
4406 case nsIFileKind::DoesNotExist:
4407 // Ignore files that got removed externally while iterating.
4408 break;
4410 return Ok{};
4411 }));
4413 return NS_OK;
4416 Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
4417 nsIFile& aLsArchiveFile) const {
4418 AssertIsOnIOThread();
4419 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4421 #ifdef DEBUG
4423 QM_TRY_INSPECT(const bool& exists,
4424 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4425 MOZ_ASSERT(!exists);
4427 #endif
4429 // Get the storage service first, we will need it at multiple places.
4430 QM_TRY_INSPECT(const auto& ss,
4431 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4432 MOZ_SELECT_OVERLOAD(do_GetService),
4433 MOZ_STORAGE_SERVICE_CONTRACTID));
4435 // Get the web apps store file.
4436 QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath));
4438 QM_TRY(MOZ_TO_RESULT(
4439 webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));
4441 // Now check if the web apps store is useable.
4442 QM_TRY_INSPECT(const auto& connection,
4443 CreateWebAppsStoreConnection(*webAppsStoreFile, *ss));
4445 if (connection) {
4446 // Find out the journal mode.
4447 QM_TRY_INSPECT(const auto& stmt,
4448 CreateAndExecuteSingleStepStatement(
4449 *connection, "PRAGMA journal_mode;"_ns));
4451 QM_TRY_INSPECT(const auto& journalMode,
4452 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt,
4453 GetUTF8String, 0));
4455 QM_TRY(MOZ_TO_RESULT(stmt->Finalize()));
4457 if (journalMode.EqualsLiteral("wal")) {
4458 // We don't copy the WAL file, so make sure the old database is fully
4459 // checkpointed.
4460 QM_TRY(MOZ_TO_RESULT(
4461 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns)));
4464 // Explicitely close the connection before the old database is copied.
4465 QM_TRY(MOZ_TO_RESULT(connection->Close()));
4467 // Copy the old database. The database is copied from
4468 // <profile>/webappsstore.sqlite to
4469 // <profile>/storage/ls-archive-tmp.sqlite
4470 // We use a "-tmp" postfix since we are not done yet.
4471 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
4473 QM_TRY(MOZ_TO_RESULT(webAppsStoreFile->CopyTo(
4474 storageDir, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
4476 QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
4477 GetLocalStorageArchiveTmpFile(*mStoragePath));
4479 if (journalMode.EqualsLiteral("wal")) {
4480 QM_TRY_INSPECT(
4481 const auto& lsArchiveTmpConnection,
4482 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4483 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4484 lsArchiveTmpFile, mozIStorageService::CONNECTION_DEFAULT));
4486 // The archive will only be used for lazy data migration. There won't be
4487 // any concurrent readers and writers that could benefit from Write-Ahead
4488 // Logging. So switch to a standard rollback journal. The standard
4489 // rollback journal also provides atomicity across multiple attached
4490 // databases which is import for the lazy data migration to work safely.
4491 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->ExecuteSimpleSQL(
4492 "PRAGMA journal_mode = DELETE;"_ns)));
4494 // Close the connection explicitly. We are going to rename the file below.
4495 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->Close()));
4498 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
4499 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile->MoveTo(
4500 nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME))));
4502 return Ok{};
4505 // If webappsstore database is not useable, just create an empty archive.
4506 // XXX The code below should be removed and the caller should call us only
4507 // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
4508 // reworked to propagate database corruption instead of returning null
4509 // connection.
4510 // So, if there's no webappsstore.sqlite
4511 // MaybeCreateOrUpgradeLocalStorageArchive will call
4512 // CreateEmptyLocalStorageArchive instead of
4513 // CopyLocalStorageArchiveFromWebAppsStore.
4514 // If there's any corruption detected during
4515 // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
4516 // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
4517 // EnsureStorageIsInitializedInternal will fallback to
4518 // CreateEmptyLocalStorageArchive.
4520 // Ensure the storage directory actually exists.
4521 QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath));
4523 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory));
4525 Unused << created;
4527 QM_TRY_UNWRAP(auto lsArchiveConnection,
4528 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4529 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4530 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4532 QM_TRY(MOZ_TO_RESULT(
4533 StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection)));
4535 return Ok{};
4538 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4539 QuotaManager::CreateLocalStorageArchiveConnection(
4540 nsIFile& aLsArchiveFile) const {
4541 AssertIsOnIOThread();
4542 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4544 #ifdef DEBUG
4546 QM_TRY_INSPECT(const bool& exists,
4547 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4548 MOZ_ASSERT(exists);
4550 #endif
4552 QM_TRY_INSPECT(const bool& isDirectory,
4553 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, IsDirectory));
4555 // A directory with the name of the archive file is treated as corruption
4556 // (similarly as wrong content of the file).
4557 QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED));
4559 QM_TRY_INSPECT(const auto& ss,
4560 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4561 MOZ_SELECT_OVERLOAD(do_GetService),
4562 MOZ_STORAGE_SERVICE_CONTRACTID));
4564 // This may return NS_ERROR_FILE_CORRUPTED too.
4565 QM_TRY_UNWRAP(auto connection,
4566 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4567 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4568 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4570 // The legacy LS implementation removes the database and creates an empty one
4571 // when the schema can't be updated. The same effect can be achieved here by
4572 // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
4573 // sub test case 3 of dom/localstorage/test/unit/test_archive.js
4574 QM_TRY(
4575 MOZ_TO_RESULT(StorageDBUpdater::Update(connection))
4576 .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; }));
4578 return connection;
4581 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4582 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
4583 nsIFile& aLsArchiveFile) {
4584 AssertIsOnIOThread();
4585 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4587 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4589 #ifdef DEBUG
4591 QM_TRY_INSPECT(const bool& exists,
4592 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4594 MOZ_ASSERT(exists);
4596 #endif
4598 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
4600 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4602 QM_TRY_UNWRAP(auto connection,
4603 CreateLocalStorageArchiveConnection(aLsArchiveFile));
4605 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
4607 return connection;
4610 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4611 QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) {
4612 AssertIsOnIOThread();
4613 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4615 QM_TRY_UNWRAP(auto connection,
4616 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4618 QM_TRY(MOZ_TO_RESULT(
4619 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
4621 return connection;
4624 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4625 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
4626 nsIFile& aLsArchiveFile) {
4627 AssertIsOnIOThread();
4628 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4630 QM_TRY_UNWRAP(auto connection,
4631 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4633 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection, 4)));
4635 return connection;
4639 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
4640 nsCOMPtr<mozIStorageConnection>& aConnection) {
4641 AssertIsOnIOThread();
4642 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4644 nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
4645 if (NS_WARN_IF(NS_FAILED(rv))) {
4646 return rv;
4649 return NS_OK;
4653 #ifdef DEBUG
4655 void QuotaManager::AssertStorageIsInitializedInternal() const {
4656 AssertIsOnIOThread();
4657 MOZ_ASSERT(IsStorageInitializedInternal());
4660 #endif // DEBUG
4662 nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
4663 nsIFile& aStorageFile) {
4664 AssertIsOnIOThread();
4666 QM_TRY_INSPECT(const auto& storageFileExists,
4667 MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile, Exists));
4669 if (!storageFileExists) {
4670 QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath));
4672 QM_TRY_INSPECT(const auto& indexedDBDirExists,
4673 MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir, Exists));
4675 if (indexedDBDirExists) {
4676 QM_TRY(MOZ_TO_RESULT(
4677 UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
4678 indexedDBDir)));
4681 QM_TRY_INSPECT(const auto& persistentStorageDir,
4682 QM_NewLocalFile(*mStoragePath));
4684 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
4685 nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
4687 QM_TRY_INSPECT(const auto& persistentStorageDirExists,
4688 MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir, Exists));
4690 if (persistentStorageDirExists) {
4691 QM_TRY(MOZ_TO_RESULT(
4692 UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
4693 persistentStorageDir)));
4697 return NS_OK;
4700 nsresult QuotaManager::MaybeCreateOrUpgradeStorage(
4701 mozIStorageConnection& aConnection) {
4702 AssertIsOnIOThread();
4704 QM_TRY_UNWRAP(auto storageVersion,
4705 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4707 // Hacky downgrade logic!
4708 // If we see major.minor of 3.0, downgrade it to be 2.1.
4709 if (storageVersion == kHackyPreDowngradeStorageVersion) {
4710 storageVersion = kHackyPostDowngradeStorageVersion;
4711 QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)),
4712 QM_PROPAGATE,
4713 [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
4716 QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
4717 NS_ERROR_FAILURE, [](const auto&) {
4718 NS_WARNING("Unable to initialize storage, version is too high!");
4721 if (storageVersion < kStorageVersion) {
4722 const bool newDatabase = !storageVersion;
4724 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
4726 QM_TRY_INSPECT(const auto& storageDirExists,
4727 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
4729 const bool newDirectory = !storageDirExists;
4731 if (newDatabase) {
4732 // Set the page size first.
4733 if (kSQLitePageSizeOverride) {
4734 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsPrintfCString(
4735 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
4739 mozStorageTransaction transaction(
4740 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
4742 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
4744 // An upgrade method can upgrade the database, the storage or both.
4745 // The upgrade loop below can only be avoided when there's no database and
4746 // no storage yet (e.g. new profile).
4747 if (newDatabase && newDirectory) {
4748 QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection)));
4750 #ifdef DEBUG
4752 QM_TRY_INSPECT(
4753 const int32_t& storageVersion,
4754 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion),
4755 QM_ASSERT_UNREACHABLE);
4756 MOZ_ASSERT(storageVersion == kStorageVersion);
4758 #endif
4760 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
4761 nsLiteralCString("INSERT INTO database (cache_version) "
4762 "VALUES (0)"))));
4763 } else {
4764 // This logic needs to change next time we change the storage!
4765 static_assert(kStorageVersion == int32_t((2 << 16) + 3),
4766 "Upgrade function needed due to storage version increase.");
4768 while (storageVersion != kStorageVersion) {
4769 if (storageVersion == 0) {
4770 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection)));
4771 } else if (storageVersion == MakeStorageVersion(1, 0)) {
4772 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection)));
4773 } else if (storageVersion == MakeStorageVersion(2, 0)) {
4774 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection)));
4775 } else if (storageVersion == MakeStorageVersion(2, 1)) {
4776 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection)));
4777 } else if (storageVersion == MakeStorageVersion(2, 2)) {
4778 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection)));
4779 } else {
4780 QM_FAIL(NS_ERROR_FAILURE, []() {
4781 NS_WARNING(
4782 "Unable to initialize storage, no upgrade path is "
4783 "available!");
4787 QM_TRY_UNWRAP(storageVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
4788 aConnection, GetSchemaVersion));
4791 MOZ_ASSERT(storageVersion == kStorageVersion);
4794 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
4797 return NS_OK;
4800 OkOrErr QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
4801 AssertIsOnIOThread();
4803 QM_TRY_INSPECT(
4804 const auto& lsArchiveTmpFile,
4805 QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath)));
4807 QM_TRY_INSPECT(const bool& exists,
4808 QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile, Exists));
4810 if (exists) {
4811 QM_TRY(QM_TO_RESULT(lsArchiveTmpFile->Remove(false)));
4814 return Ok{};
4817 Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
4818 nsIFile& aLsArchiveFile) {
4819 AssertIsOnIOThread();
4821 QM_TRY_INSPECT(
4822 const bool& lsArchiveFileExisted,
4823 ([this, &aLsArchiveFile]() -> Result<bool, nsresult> {
4824 QM_TRY_INSPECT(const bool& exists,
4825 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4827 if (!exists) {
4828 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4831 return exists;
4832 }()));
4834 QM_TRY_UNWRAP(auto connection,
4835 CreateLocalStorageArchiveConnection(aLsArchiveFile));
4837 QM_TRY_INSPECT(const auto& initialized,
4838 IsLocalStorageArchiveInitialized(*connection));
4840 if (!initialized) {
4841 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
4844 QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection));
4846 if (version > kLocalStorageArchiveVersion) {
4847 // Close local storage archive connection. We are going to remove underlying
4848 // file.
4849 QM_TRY(MOZ_TO_RESULT(connection->Close()));
4851 // This will wipe the archive and any migrated data and recopy the archive
4852 // from webappsstore.sqlite.
4853 QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile));
4855 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
4857 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
4858 } else if (version != kLocalStorageArchiveVersion) {
4859 // The version can be zero either when the archive didn't exist or it did
4860 // exist, but the archive was created without any version information.
4861 // We don't need to do any upgrades only if it didn't exist because existing
4862 // archives without version information must be recopied to really fix bug
4863 // 1542104. See also bug 1546305 which introduced archive versions.
4864 if (!lsArchiveFileExisted) {
4865 MOZ_ASSERT(version == 0);
4867 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(
4868 connection, kLocalStorageArchiveVersion)));
4869 } else {
4870 static_assert(kLocalStorageArchiveVersion == 4,
4871 "Upgrade function needed due to LocalStorage archive "
4872 "version increase.");
4874 while (version != kLocalStorageArchiveVersion) {
4875 if (version < 4) {
4876 // Close local storage archive connection. We are going to remove
4877 // underlying file.
4878 QM_TRY(MOZ_TO_RESULT(connection->Close()));
4880 // This won't do an "upgrade" in a normal sense. It will wipe the
4881 // archive and any migrated data and recopy the archive from
4882 // webappsstore.sqlite
4883 QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4(
4884 aLsArchiveFile));
4885 } /* else if (version == 4) {
4886 QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection)));
4887 } */
4888 else {
4889 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
4890 QM_WARNING(
4891 "Unable to initialize LocalStorage archive, no upgrade path "
4892 "is available!");
4896 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
4899 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
4903 // At this point, we have finished initializing the local storage archive, and
4904 // can continue storage initialization. We don't know though if the actual
4905 // data in the archive file is readable. We can't do a PRAGMA integrity_check
4906 // here though, because that would be too heavyweight.
4908 return Ok{};
4911 Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive(
4912 nsIFile& aLsArchiveFile) const {
4913 AssertIsOnIOThread();
4915 QM_TRY_INSPECT(const bool& exists,
4916 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4918 // If it exists, remove it. It might be a directory, so remove it recursively.
4919 if (exists) {
4920 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(true)));
4922 // XXX If we crash right here, the next session will copy the archive from
4923 // webappsstore.sqlite again!
4924 // XXX Create a marker file before removing the archive which can be
4925 // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
4926 // archive instead of recopying it from webapppstore.sqlite (in other
4927 // words, finishing what was started here).
4930 QM_TRY_INSPECT(const auto& ss,
4931 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4932 MOZ_SELECT_OVERLOAD(do_GetService),
4933 MOZ_STORAGE_SERVICE_CONTRACTID));
4935 QM_TRY_UNWRAP(const auto connection,
4936 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4937 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4938 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4940 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));
4942 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
4944 QM_TRY(MOZ_TO_RESULT(
4945 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
4947 return Ok{};
4950 RefPtr<BoolPromise> QuotaManager::InitializeStorage() {
4951 AssertIsOnOwningThread();
4953 // If storage is initialized but there's a clear storage or shutdown storage
4954 // operation already scheduled, we can't immediately resolve the promise and
4955 // return from the function because the clear or shutdown storage operation
4956 // uninitializes storage.
4957 if (mStorageInitialized && !mShutdownStorageOpCount) {
4958 return BoolPromise::CreateAndResolve(true, __func__);
4961 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
4962 Nullable<PersistenceType>(), OriginScope::FromNull(),
4963 Nullable<Client::Type>(),
4964 /* aExclusive */ false);
4966 return directoryLock->Acquire()->Then(
4967 GetCurrentSerialEventTarget(), __func__,
4968 [self = RefPtr(this),
4969 directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
4970 if (aValue.IsReject()) {
4971 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
4974 return self->InitializeStorage(std::move(directoryLock));
4978 RefPtr<BoolPromise> QuotaManager::InitializeStorage(
4979 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
4980 AssertIsOnOwningThread();
4981 MOZ_ASSERT(aDirectoryLock);
4983 if (mStorageInitialized && !mShutdownStorageOpCount) {
4984 return BoolPromise::CreateAndResolve(true, __func__);
4987 auto initializeStorageOp =
4988 CreateInitOp(WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
4990 RegisterNormalOriginOp(*initializeStorageOp);
4992 initializeStorageOp->RunImmediately();
4994 return initializeStorageOp->OnResults()->Then(
4995 GetCurrentSerialEventTarget(), __func__,
4996 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
4997 if (aValue.IsReject()) {
4998 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5001 self->mStorageInitialized = true;
5003 return BoolPromise::CreateAndResolve(true, __func__);
5007 RefPtr<BoolPromise> QuotaManager::StorageInitialized() {
5008 AssertIsOnOwningThread();
5010 auto storageInitializedOp =
5011 CreateStorageInitializedOp(WrapMovingNotNullUnchecked(this));
5013 RegisterNormalOriginOp(*storageInitializedOp);
5015 storageInitializedOp->RunImmediately();
5017 return storageInitializedOp->OnResults();
5020 nsresult QuotaManager::EnsureStorageIsInitializedInternal() {
5021 DiagnosticAssertIsOnIOThread();
5023 const auto innerFunc =
5024 [&](const auto& firstInitializationAttempt) -> nsresult {
5025 if (mStorageConnection) {
5026 MOZ_ASSERT(firstInitializationAttempt.Recorded());
5027 return NS_OK;
5030 QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath));
5031 QM_TRY(MOZ_TO_RESULT(storageFile->Append(mStorageName + kSQLiteSuffix)));
5033 QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile)));
5035 QM_TRY_INSPECT(const auto& ss,
5036 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
5037 MOZ_SELECT_OVERLOAD(do_GetService),
5038 MOZ_STORAGE_SERVICE_CONTRACTID));
5040 QM_TRY_UNWRAP(
5041 auto connection,
5042 QM_OR_ELSE_WARN_IF(
5043 // Expression.
5044 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5045 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
5046 storageFile, mozIStorageService::CONNECTION_DEFAULT),
5047 // Predicate.
5048 IsDatabaseCorruptionError,
5049 // Fallback.
5050 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
5052 if (!connection) {
5053 // Nuke the database file.
5054 QM_TRY(MOZ_TO_RESULT(storageFile->Remove(false)));
5056 QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5057 nsCOMPtr<mozIStorageConnection>, ss,
5058 OpenUnsharedDatabase, storageFile,
5059 mozIStorageService::CONNECTION_DEFAULT));
5062 // We want extra durability for this important file.
5063 QM_TRY(MOZ_TO_RESULT(
5064 connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns)));
5066 // Check to make sure that the storage version is correct.
5067 QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection)));
5069 QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
5071 QM_TRY_INSPECT(const auto& lsArchiveFile,
5072 GetLocalStorageArchiveFile(*mStoragePath));
5074 if (CachedNextGenLocalStorageEnabled()) {
5075 QM_TRY(QM_OR_ELSE_WARN_IF(
5076 // Expression.
5077 MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile),
5078 // Predicate.
5079 IsDatabaseCorruptionError,
5080 // Fallback.
5081 ([&](const nsresult rv) -> Result<Ok, nsresult> {
5082 QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile));
5083 })));
5084 } else {
5085 QM_TRY(
5086 MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile)));
5089 QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection));
5091 if (mCacheUsable && gInvalidateQuotaCache) {
5092 QM_TRY(InvalidateCache(*connection));
5094 gInvalidateQuotaCache = false;
5097 mStorageConnection = std::move(connection);
5099 return NS_OK;
5102 return ExecuteInitialization(
5103 Initialization::Storage,
5104 "dom::quota::FirstInitializationAttempt::Storage"_ns, innerFunc);
5107 RefPtr<BoolPromise> QuotaManager::TemporaryStorageInitialized() {
5108 AssertIsOnOwningThread();
5110 auto temporaryStorageInitializedOp =
5111 CreateTemporaryStorageInitializedOp(WrapMovingNotNullUnchecked(this));
5113 RegisterNormalOriginOp(*temporaryStorageInitializedOp);
5115 temporaryStorageInitializedOp->RunImmediately();
5117 return temporaryStorageInitializedOp->OnResults();
5120 RefPtr<UniversalDirectoryLockPromise> QuotaManager::OpenStorageDirectory(
5121 const Nullable<PersistenceType>& aPersistenceType,
5122 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
5123 bool aExclusive, DirectoryLockCategory aCategory,
5124 Maybe<RefPtr<UniversalDirectoryLock>&> aPendingDirectoryLockOut) {
5125 AssertIsOnOwningThread();
5127 RefPtr<UniversalDirectoryLock> storageDirectoryLock;
5129 RefPtr<BoolPromise> storageDirectoryLockPromise;
5131 if (mStorageInitialized && !mShutdownStorageOpCount) {
5132 storageDirectoryLockPromise = BoolPromise::CreateAndResolve(true, __func__);
5133 } else {
5134 storageDirectoryLock = CreateDirectoryLockInternal(
5135 Nullable<PersistenceType>(), OriginScope::FromNull(),
5136 Nullable<Client::Type>(),
5137 /* aExclusive */ false);
5139 storageDirectoryLockPromise = storageDirectoryLock->Acquire();
5142 RefPtr<UniversalDirectoryLock> universalDirectoryLock =
5143 CreateDirectoryLockInternal(aPersistenceType, aOriginScope, aClientType,
5144 aExclusive, aCategory);
5146 RefPtr<BoolPromise> universalDirectoryLockPromise =
5147 universalDirectoryLock->Acquire();
5149 if (aPendingDirectoryLockOut.isSome()) {
5150 aPendingDirectoryLockOut.ref() = universalDirectoryLock;
5153 return storageDirectoryLockPromise
5154 ->Then(GetCurrentSerialEventTarget(), __func__,
5155 [self = RefPtr(this),
5156 storageDirectoryLock = std::move(storageDirectoryLock)](
5157 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5158 if (aValue.IsReject()) {
5159 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5160 __func__);
5163 if (!storageDirectoryLock) {
5164 return BoolPromise::CreateAndResolve(true, __func__);
5167 return self->InitializeStorage(std::move(storageDirectoryLock));
5169 ->Then(GetCurrentSerialEventTarget(), __func__,
5170 [universalDirectoryLockPromise =
5171 std::move(universalDirectoryLockPromise)](
5172 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5173 if (aValue.IsReject()) {
5174 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5175 __func__);
5178 return std::move(universalDirectoryLockPromise);
5180 ->Then(GetCurrentSerialEventTarget(), __func__,
5181 [universalDirectoryLock = std::move(universalDirectoryLock)](
5182 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5183 if (aValue.IsReject()) {
5184 return UniversalDirectoryLockPromise::CreateAndReject(
5185 aValue.RejectValue(), __func__);
5188 return UniversalDirectoryLockPromise::CreateAndResolve(
5189 std::move(universalDirectoryLock), __func__);
5193 RefPtr<ClientDirectoryLockPromise> QuotaManager::OpenClientDirectory(
5194 const ClientMetadata& aClientMetadata,
5195 Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) {
5196 AssertIsOnOwningThread();
5198 nsTArray<RefPtr<BoolPromise>> promises;
5200 RefPtr<UniversalDirectoryLock> storageDirectoryLock;
5202 if (!mStorageInitialized || mShutdownStorageOpCount) {
5203 storageDirectoryLock = CreateDirectoryLockInternal(
5204 Nullable<PersistenceType>(), OriginScope::FromNull(),
5205 Nullable<Client::Type>(),
5206 /* aExclusive */ false);
5207 promises.AppendElement(storageDirectoryLock->Acquire());
5210 RefPtr<ClientDirectoryLock> clientDirectoryLock =
5211 CreateDirectoryLock(aClientMetadata, /* aExclusive */ false);
5213 promises.AppendElement(clientDirectoryLock->Acquire());
5215 if (aPendingDirectoryLockOut.isSome()) {
5216 aPendingDirectoryLockOut.ref() = clientDirectoryLock;
5219 return BoolPromise::All(GetCurrentSerialEventTarget(), promises)
5220 ->Then(
5221 GetCurrentSerialEventTarget(), __func__,
5222 [self = RefPtr(this),
5223 storageDirectoryLock = std::move(storageDirectoryLock)](
5224 const CopyableTArray<bool>& aResolveValues) mutable {
5225 if (!storageDirectoryLock) {
5226 return BoolPromise::CreateAndResolve(true, __func__);
5229 return self->InitializeStorage(std::move(storageDirectoryLock));
5231 [](nsresult aRejectValue) {
5232 return BoolPromise::CreateAndReject(aRejectValue, __func__);
5234 ->Then(GetCurrentSerialEventTarget(), __func__,
5235 [clientDirectoryLock = std::move(clientDirectoryLock)](
5236 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5237 if (aValue.IsReject()) {
5238 return ClientDirectoryLockPromise::CreateAndReject(
5239 aValue.RejectValue(), __func__);
5241 return ClientDirectoryLockPromise::CreateAndResolve(
5242 std::move(clientDirectoryLock), __func__);
5246 RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
5247 const ClientMetadata& aClientMetadata, bool aExclusive) {
5248 AssertIsOnOwningThread();
5250 return DirectoryLockImpl::Create(
5251 WrapNotNullUnchecked(this), aClientMetadata.mPersistenceType,
5252 aClientMetadata, aClientMetadata.mClientType, aExclusive);
5255 RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
5256 const Nullable<PersistenceType>& aPersistenceType,
5257 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
5258 bool aExclusive, DirectoryLockCategory aCategory) {
5259 AssertIsOnOwningThread();
5261 return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
5262 aPersistenceType, aOriginScope,
5263 aClientType, aExclusive, aCategory);
5266 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5267 QuotaManager::EnsurePersistentOriginIsInitialized(
5268 const OriginMetadata& aOriginMetadata) {
5269 AssertIsOnIOThread();
5270 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5271 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5273 const auto innerFunc = [&aOriginMetadata,
5274 this](const auto& firstInitializationAttempt)
5275 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
5276 const auto extraInfo =
5277 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
5278 aOriginMetadata.mStorageOrigin};
5280 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
5282 if (mInitializedOrigins.Contains(aOriginMetadata.mOrigin)) {
5283 MOZ_ASSERT(firstInitializationAttempt.Recorded());
5284 return std::pair(std::move(directory), false);
5287 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
5289 QM_TRY_INSPECT(
5290 const int64_t& timestamp,
5291 ([this, created, &directory,
5292 &aOriginMetadata]() -> Result<int64_t, nsresult> {
5293 if (created) {
5294 const int64_t timestamp = PR_Now();
5296 // Only creating .metadata-v2 to reduce IO.
5297 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
5298 /* aPersisted */ true,
5299 aOriginMetadata)));
5301 return timestamp;
5304 // Get the metadata. We only use the timestamp.
5305 QM_TRY_INSPECT(const auto& metadata,
5306 LoadFullOriginMetadataWithRestore(directory));
5308 MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now());
5310 return metadata.mLastAccessTime;
5311 }()));
5313 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT,
5314 aOriginMetadata, timestamp,
5315 /* aPersisted */ true, directory)));
5317 mInitializedOrigins.AppendElement(aOriginMetadata.mOrigin);
5319 return std::pair(std::move(directory), created);
5322 return ExecuteOriginInitialization(
5323 aOriginMetadata.mOrigin, OriginInitialization::PersistentOrigin,
5324 "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns,
5325 innerFunc);
5328 bool QuotaManager::IsTemporaryOriginInitialized(
5329 const OriginMetadata& aOriginMetadata) const {
5330 AssertIsOnIOThread();
5331 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5333 MutexAutoLock lock(mQuotaMutex);
5335 RefPtr<OriginInfo> originInfo =
5336 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
5338 return static_cast<bool>(originInfo);
5341 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5342 QuotaManager::EnsureTemporaryOriginIsInitialized(
5343 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
5344 AssertIsOnIOThread();
5345 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5346 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
5347 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5348 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
5350 const auto innerFunc = [&aOriginMetadata, this](const auto&)
5351 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
5352 // Get directory for this origin and persistence type.
5353 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
5355 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
5357 if (created) {
5358 const int64_t timestamp =
5359 NoteOriginDirectoryCreated(aOriginMetadata, /* aPersisted */ false);
5361 // Only creating .metadata-v2 to reduce IO.
5362 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
5363 /* aPersisted */ false,
5364 aOriginMetadata)));
5367 // TODO: If the metadata file exists and we didn't call
5368 // LoadFullOriginMetadataWithRestore for it (because the quota info
5369 // was loaded from the cache), then the group in the metadata file
5370 // may be wrong, so it should be checked and eventually updated.
5371 // It's not a big deal that we are not doing it here, because the
5372 // origin will be marked as "accessed", so
5373 // LoadFullOriginMetadataWithRestore will be called for the metadata
5374 // file in next session in LoadQuotaFromCache.
5376 return std::pair(std::move(directory), created);
5379 return ExecuteOriginInitialization(
5380 aOriginMetadata.mOrigin, OriginInitialization::TemporaryOrigin,
5381 "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns,
5382 innerFunc);
5385 RefPtr<BoolPromise> QuotaManager::InitializePersistentClient(
5386 const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
5387 AssertIsOnOwningThread();
5389 auto initializePersistentClientOp = CreateInitializePersistentClientOp(
5390 WrapMovingNotNullUnchecked(this), aPrincipalInfo, aClientType);
5392 RegisterNormalOriginOp(*initializePersistentClientOp);
5394 initializePersistentClientOp->RunImmediately();
5396 return initializePersistentClientOp->OnResults();
5399 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5400 QuotaManager::EnsurePersistentClientIsInitialized(
5401 const ClientMetadata& aClientMetadata) {
5402 AssertIsOnIOThread();
5403 MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5404 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
5405 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
5406 MOZ_DIAGNOSTIC_ASSERT(IsOriginInitialized(aClientMetadata.mOrigin));
5408 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
5410 QM_TRY(MOZ_TO_RESULT(
5411 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
5413 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
5415 return std::pair(std::move(directory), created);
5418 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient(
5419 PersistenceType aPersistenceType, const PrincipalInfo& aPrincipalInfo,
5420 Client::Type aClientType) {
5421 AssertIsOnOwningThread();
5423 auto initializeTemporaryClientOp = CreateInitializeTemporaryClientOp(
5424 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo,
5425 aClientType);
5427 RegisterNormalOriginOp(*initializeTemporaryClientOp);
5429 initializeTemporaryClientOp->RunImmediately();
5431 return initializeTemporaryClientOp->OnResults();
5434 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5435 QuotaManager::EnsureTemporaryClientIsInitialized(
5436 const ClientMetadata& aClientMetadata) {
5437 AssertIsOnIOThread();
5438 MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5439 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
5440 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
5441 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
5442 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryOriginInitialized(aClientMetadata));
5444 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
5446 QM_TRY(MOZ_TO_RESULT(
5447 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
5449 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
5451 return std::pair(std::move(directory), created);
5454 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage() {
5455 AssertIsOnOwningThread();
5457 // If temporary storage is initialized but there's a clear storage or
5458 // shutdown storage operation already scheduled, we can't immediately resolve
5459 // the promise and return from the function because the clear or shutdown
5460 // storage operation uninitializes storage.
5461 if (mTemporaryStorageInitialized && !mShutdownStorageOpCount) {
5462 return BoolPromise::CreateAndResolve(true, __func__);
5465 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
5466 Nullable<PersistenceType>(), OriginScope::FromNull(),
5467 Nullable<Client::Type>(),
5468 /* aExclusive */ false);
5470 return directoryLock->Acquire()->Then(
5471 GetCurrentSerialEventTarget(), __func__,
5472 [self = RefPtr(this),
5473 directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5474 if (aValue.IsReject()) {
5475 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5478 return self->InitializeTemporaryStorage(std::move(directoryLock));
5482 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage(
5483 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
5484 AssertIsOnOwningThread();
5485 MOZ_ASSERT(aDirectoryLock);
5487 if (mTemporaryStorageInitialized && !mShutdownStorageOpCount) {
5488 return BoolPromise::CreateAndResolve(true, __func__);
5491 auto initializeTemporaryStorageOp = CreateInitTemporaryStorageOp(
5492 WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
5494 RegisterNormalOriginOp(*initializeTemporaryStorageOp);
5496 initializeTemporaryStorageOp->RunImmediately();
5498 return initializeTemporaryStorageOp->OnResults()->Then(
5499 GetCurrentSerialEventTarget(), __func__,
5500 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5501 if (aValue.IsReject()) {
5502 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5505 self->mTemporaryStorageInitialized = true;
5507 return BoolPromise::CreateAndResolve(true, __func__);
5511 nsresult QuotaManager::EnsureTemporaryStorageIsInitializedInternal() {
5512 AssertIsOnIOThread();
5513 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5515 const auto innerFunc =
5516 [&](const auto& firstInitializationAttempt) -> nsresult {
5517 if (mTemporaryStorageInitializedInternal) {
5518 MOZ_ASSERT(firstInitializationAttempt.Recorded());
5519 return NS_OK;
5522 QM_TRY_INSPECT(
5523 const auto& storageDir,
5524 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFile>,
5525 MOZ_SELECT_OVERLOAD(do_CreateInstance),
5526 NS_LOCAL_FILE_CONTRACTID));
5528 QM_TRY(MOZ_TO_RESULT(storageDir->InitWithPath(GetStoragePath())));
5530 // The storage directory must exist before calling GetTemporaryStorageLimit.
5531 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir));
5533 Unused << created;
5535 QM_TRY_UNWRAP(mTemporaryStorageLimit,
5536 GetTemporaryStorageLimit(*storageDir));
5538 QM_TRY(MOZ_TO_RESULT(LoadQuota()));
5540 mTemporaryStorageInitializedInternal = true;
5542 CleanupTemporaryStorage();
5544 if (mCacheUsable) {
5545 QM_TRY(InvalidateCache(*mStorageConnection));
5548 return NS_OK;
5551 return ExecuteInitialization(
5552 Initialization::TemporaryStorage,
5553 "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc);
5556 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOrigin(
5557 const Maybe<PersistenceType>& aPersistenceType,
5558 const PrincipalInfo& aPrincipalInfo,
5559 const Maybe<Client::Type>& aClientType) {
5560 AssertIsOnOwningThread();
5562 auto clearOriginOp =
5563 CreateClearOriginOp(WrapMovingNotNullUnchecked(this), aPersistenceType,
5564 aPrincipalInfo, aClientType);
5566 RegisterNormalOriginOp(*clearOriginOp);
5568 clearOriginOp->RunImmediately();
5570 return clearOriginOp->OnResults();
5573 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginPrefix(
5574 const Maybe<PersistenceType>& aPersistenceType,
5575 const PrincipalInfo& aPrincipalInfo) {
5576 AssertIsOnOwningThread();
5578 auto clearStoragesForOriginPrefixOp = CreateClearStoragesForOriginPrefixOp(
5579 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo);
5581 RegisterNormalOriginOp(*clearStoragesForOriginPrefixOp);
5583 clearStoragesForOriginPrefixOp->RunImmediately();
5585 return clearStoragesForOriginPrefixOp->OnResults();
5588 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginAttributesPattern(
5589 const OriginAttributesPattern& aPattern) {
5590 AssertIsOnOwningThread();
5592 auto clearDataOp =
5593 CreateClearDataOp(WrapMovingNotNullUnchecked(this), aPattern);
5595 RegisterNormalOriginOp(*clearDataOp);
5597 clearDataOp->RunImmediately();
5599 return clearDataOp->OnResults();
5602 RefPtr<BoolPromise> QuotaManager::ClearPrivateRepository() {
5603 AssertIsOnOwningThread();
5605 auto clearPrivateRepositoryOp =
5606 CreateClearPrivateRepositoryOp(WrapMovingNotNullUnchecked(this));
5608 RegisterNormalOriginOp(*clearPrivateRepositoryOp);
5610 clearPrivateRepositoryOp->RunImmediately();
5612 return clearPrivateRepositoryOp->OnResults();
5615 RefPtr<BoolPromise> QuotaManager::ClearStorage() {
5616 AssertIsOnOwningThread();
5618 auto clearStorageOp = CreateClearStorageOp(WrapMovingNotNullUnchecked(this));
5620 RegisterNormalOriginOp(*clearStorageOp);
5622 clearStorageOp->RunImmediately();
5624 // Storage clearing also shuts it down, so we need to increses the counter
5625 // here as well.
5626 mShutdownStorageOpCount++;
5628 return clearStorageOp->OnResults()->Then(
5629 GetCurrentSerialEventTarget(), __func__,
5630 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5631 self->mShutdownStorageOpCount--;
5633 if (aValue.IsReject()) {
5634 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5637 self->mTemporaryStorageInitialized = false;
5638 self->mStorageInitialized = false;
5640 return BoolPromise::CreateAndResolve(true, __func__);
5644 RefPtr<BoolPromise> QuotaManager::ShutdownStorage() {
5645 AssertIsOnOwningThread();
5647 auto shutdownStorageOp =
5648 CreateShutdownStorageOp(WrapMovingNotNullUnchecked(this));
5650 RegisterNormalOriginOp(*shutdownStorageOp);
5652 shutdownStorageOp->RunImmediately();
5654 mShutdownStorageOpCount++;
5656 return shutdownStorageOp->OnResults()->Then(
5657 GetCurrentSerialEventTarget(), __func__,
5658 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5659 self->mShutdownStorageOpCount--;
5661 if (aValue.IsReject()) {
5662 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5665 self->mTemporaryStorageInitialized = false;
5666 self->mStorageInitialized = false;
5668 return BoolPromise::CreateAndResolve(true, __func__);
5672 void QuotaManager::ShutdownStorageInternal() {
5673 AssertIsOnIOThread();
5675 if (mStorageConnection) {
5676 mInitializationInfo.ResetOriginInitializationInfos();
5677 mInitializedOrigins.Clear();
5679 if (mTemporaryStorageInitializedInternal) {
5680 if (mCacheUsable) {
5681 UnloadQuota();
5682 } else {
5683 RemoveQuota();
5686 mTemporaryStorageInitializedInternal = false;
5689 ReleaseIOThreadObjects();
5691 mStorageConnection = nullptr;
5692 mCacheUsable = false;
5695 mInitializationInfo.ResetFirstInitializationAttempts();
5698 Result<bool, nsresult> QuotaManager::EnsureOriginDirectory(
5699 nsIFile& aDirectory) {
5700 AssertIsOnIOThread();
5702 QM_TRY_INSPECT(const bool& exists,
5703 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
5705 if (!exists) {
5706 QM_TRY_INSPECT(
5707 const auto& leafName,
5708 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aDirectory, GetLeafName)
5709 .map([](const auto& leafName) {
5710 return NS_ConvertUTF16toUTF8(leafName);
5711 }));
5713 QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE),
5714 [](const auto&) {
5715 QM_WARNING(
5716 "Preventing creation of a new origin directory which is not "
5717 "supported by our origin parser or is obsolete!");
5721 QM_TRY_RETURN(EnsureDirectory(aDirectory));
5724 nsresult QuotaManager::AboutToClearOrigins(
5725 const Nullable<PersistenceType>& aPersistenceType,
5726 const OriginScope& aOriginScope,
5727 const Nullable<Client::Type>& aClientType) {
5728 AssertIsOnIOThread();
5730 if (aClientType.IsNull()) {
5731 for (Client::Type type : AllClientTypes()) {
5732 QM_TRY(MOZ_TO_RESULT((*mClients)[type]->AboutToClearOrigins(
5733 aPersistenceType, aOriginScope)));
5735 } else {
5736 QM_TRY(MOZ_TO_RESULT((*mClients)[aClientType.Value()]->AboutToClearOrigins(
5737 aPersistenceType, aOriginScope)));
5740 return NS_OK;
5743 void QuotaManager::OriginClearCompleted(
5744 PersistenceType aPersistenceType, const nsACString& aOrigin,
5745 const Nullable<Client::Type>& aClientType) {
5746 AssertIsOnIOThread();
5748 if (aClientType.IsNull()) {
5749 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
5750 mInitializedOrigins.RemoveElement(aOrigin);
5753 for (Client::Type type : AllClientTypes()) {
5754 (*mClients)[type]->OnOriginClearCompleted(aPersistenceType, aOrigin);
5756 } else {
5757 (*mClients)[aClientType.Value()]->OnOriginClearCompleted(aPersistenceType,
5758 aOrigin);
5762 void QuotaManager::RepositoryClearCompleted(PersistenceType aPersistenceType) {
5763 AssertIsOnIOThread();
5765 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
5766 mInitializedOrigins.Clear();
5769 for (Client::Type type : AllClientTypes()) {
5770 (*mClients)[type]->OnRepositoryClearCompleted(aPersistenceType);
5774 Client* QuotaManager::GetClient(Client::Type aClientType) {
5775 MOZ_ASSERT(aClientType >= Client::IDB);
5776 MOZ_ASSERT(aClientType < Client::TypeMax());
5778 return (*mClients)[aClientType];
5781 const AutoTArray<Client::Type, Client::TYPE_MAX>&
5782 QuotaManager::AllClientTypes() {
5783 if (CachedNextGenLocalStorageEnabled()) {
5784 return *mAllClientTypes;
5786 return *mAllClientTypesExceptLS;
5789 uint64_t QuotaManager::GetGroupLimit() const {
5790 // To avoid one group evicting all the rest, limit the amount any one group
5791 // can use to 20% resp. a fifth. To prevent individual sites from using
5792 // exorbitant amounts of storage where there is a lot of free space, cap the
5793 // group limit to 10GB.
5794 const auto x = std::min<uint64_t>(mTemporaryStorageLimit / 5, 10 GB);
5796 // In low-storage situations, make an exception (while not exceeding the total
5797 // storage limit).
5798 return std::min<uint64_t>(mTemporaryStorageLimit,
5799 std::max<uint64_t>(x, 10 MB));
5802 std::pair<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate(
5803 const OriginMetadata& aOriginMetadata) {
5804 AssertIsOnIOThread();
5806 uint64_t totalGroupUsage = 0;
5809 MutexAutoLock lock(mQuotaMutex);
5811 GroupInfoPair* pair;
5812 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
5813 for (const PersistenceType type : kBestEffortPersistenceTypes) {
5814 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
5815 if (groupInfo) {
5816 if (type == PERSISTENCE_TYPE_DEFAULT) {
5817 RefPtr<OriginInfo> originInfo =
5818 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
5820 if (originInfo && originInfo->LockedPersisted()) {
5821 return std::pair(mTemporaryStorageUsage, mTemporaryStorageLimit);
5825 AssertNoOverflow(totalGroupUsage, groupInfo->mUsage);
5826 totalGroupUsage += groupInfo->mUsage;
5832 return std::pair(totalGroupUsage, GetGroupLimit());
5835 uint64_t QuotaManager::GetOriginUsage(
5836 const PrincipalMetadata& aPrincipalMetadata) {
5837 AssertIsOnIOThread();
5839 uint64_t usage = 0;
5842 MutexAutoLock lock(mQuotaMutex);
5844 GroupInfoPair* pair;
5845 if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) {
5846 for (const PersistenceType type : kBestEffortPersistenceTypes) {
5847 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
5848 if (groupInfo) {
5849 RefPtr<OriginInfo> originInfo =
5850 groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin);
5851 if (originInfo) {
5852 AssertNoOverflow(usage, originInfo->LockedUsage());
5853 usage += originInfo->LockedUsage();
5860 return usage;
5863 Maybe<FullOriginMetadata> QuotaManager::GetFullOriginMetadata(
5864 const OriginMetadata& aOriginMetadata) {
5865 AssertIsOnIOThread();
5866 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5867 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
5869 MutexAutoLock lock(mQuotaMutex);
5871 RefPtr<OriginInfo> originInfo =
5872 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
5873 if (originInfo) {
5874 return Some(originInfo->LockedFlattenToFullOriginMetadata());
5877 return Nothing();
5880 void QuotaManager::NotifyStoragePressure(uint64_t aUsage) {
5881 mQuotaMutex.AssertNotCurrentThreadOwns();
5883 RefPtr<StoragePressureRunnable> storagePressureRunnable =
5884 new StoragePressureRunnable(aUsage);
5886 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable));
5889 // static
5890 void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
5891 const nsACString& aOrigin,
5892 Client::Type aClientType,
5893 nsACString& aDatabaseId) {
5894 nsAutoCString str;
5895 str.AppendInt(aPersistenceType);
5896 str.Append('*');
5897 str.Append(aOrigin);
5898 str.Append('*');
5899 str.AppendInt(aClientType);
5901 aDatabaseId = str;
5904 // static
5905 bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo) {
5906 switch (aPrincipalInfo.type()) {
5907 // A system principal is acceptable.
5908 case PrincipalInfo::TSystemPrincipalInfo: {
5909 return true;
5912 // Validate content principals to ensure that the spec, originNoSuffix and
5913 // baseDomain are sane.
5914 case PrincipalInfo::TContentPrincipalInfo: {
5915 const ContentPrincipalInfo& info =
5916 aPrincipalInfo.get_ContentPrincipalInfo();
5918 // Verify the principal spec parses.
5919 nsCOMPtr<nsIURI> uri;
5920 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), info.spec())), false);
5922 nsCOMPtr<nsIPrincipal> principal =
5923 BasePrincipal::CreateContentPrincipal(uri, info.attrs());
5924 QM_TRY(MOZ_TO_RESULT(principal), false);
5926 // Verify the principal originNoSuffix matches spec.
5927 QM_TRY_INSPECT(const auto& originNoSuffix,
5928 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
5929 GetOriginNoSuffix),
5930 false);
5932 if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
5933 QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
5934 originNoSuffix.get(), info.originNoSuffix().get());
5935 return false;
5938 if (NS_WARN_IF(info.originNoSuffix().EqualsLiteral(kChromeOrigin))) {
5939 return false;
5942 if (NS_WARN_IF(info.originNoSuffix().FindChar('^', 0) != -1)) {
5943 QM_WARNING("originNoSuffix (%s) contains the '^' character!",
5944 info.originNoSuffix().get());
5945 return false;
5948 // Verify the principal baseDomain exists.
5949 if (NS_WARN_IF(info.baseDomain().IsVoid())) {
5950 return false;
5953 // Verify the principal baseDomain matches spec.
5954 QM_TRY_INSPECT(const auto& baseDomain,
5955 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
5956 GetBaseDomain),
5957 false);
5959 if (NS_WARN_IF(baseDomain != info.baseDomain())) {
5960 QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
5961 baseDomain.get(), info.baseDomain().get());
5962 return false;
5965 return true;
5968 default: {
5969 break;
5973 // Null and expanded principals are not acceptable.
5974 return false;
5977 Result<PrincipalMetadata, nsresult>
5978 QuotaManager::GetInfoFromValidatedPrincipalInfo(
5979 const PrincipalInfo& aPrincipalInfo) {
5980 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
5982 switch (aPrincipalInfo.type()) {
5983 case PrincipalInfo::TSystemPrincipalInfo: {
5984 return GetInfoForChrome();
5987 case PrincipalInfo::TContentPrincipalInfo: {
5988 const ContentPrincipalInfo& info =
5989 aPrincipalInfo.get_ContentPrincipalInfo();
5991 nsCString suffix;
5992 info.attrs().CreateSuffix(suffix);
5994 nsCString origin = info.originNoSuffix() + suffix;
5996 if (IsUUIDOrigin(origin)) {
5997 QM_TRY_INSPECT(const auto& originalOrigin,
5998 GetOriginFromStorageOrigin(origin));
6000 nsCOMPtr<nsIPrincipal> principal =
6001 BasePrincipal::CreateContentPrincipal(originalOrigin);
6002 QM_TRY(MOZ_TO_RESULT(principal));
6004 PrincipalInfo principalInfo;
6005 QM_TRY(
6006 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
6008 return GetInfoFromValidatedPrincipalInfo(principalInfo);
6011 PrincipalMetadata principalMetadata;
6013 principalMetadata.mSuffix = suffix;
6015 principalMetadata.mGroup = info.baseDomain() + suffix;
6017 principalMetadata.mOrigin = origin;
6019 if (info.attrs().mPrivateBrowsingId != 0) {
6020 QM_TRY_UNWRAP(principalMetadata.mStorageOrigin,
6021 EnsureStorageOriginFromOrigin(origin));
6022 } else {
6023 principalMetadata.mStorageOrigin = origin;
6026 principalMetadata.mIsPrivate = info.attrs().mPrivateBrowsingId != 0;
6028 return principalMetadata;
6031 default: {
6032 MOZ_ASSERT_UNREACHABLE("Should never get here!");
6033 return Err(NS_ERROR_UNEXPECTED);
6038 // static
6039 nsAutoCString QuotaManager::GetOriginFromValidatedPrincipalInfo(
6040 const PrincipalInfo& aPrincipalInfo) {
6041 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
6043 switch (aPrincipalInfo.type()) {
6044 case PrincipalInfo::TSystemPrincipalInfo: {
6045 return nsAutoCString{GetOriginForChrome()};
6048 case PrincipalInfo::TContentPrincipalInfo: {
6049 const ContentPrincipalInfo& info =
6050 aPrincipalInfo.get_ContentPrincipalInfo();
6052 nsAutoCString suffix;
6054 info.attrs().CreateSuffix(suffix);
6056 return info.originNoSuffix() + suffix;
6059 default: {
6060 MOZ_CRASH("Should never get here!");
6065 // static
6066 Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromPrincipal(
6067 nsIPrincipal* aPrincipal) {
6068 MOZ_ASSERT(aPrincipal);
6070 if (aPrincipal->IsSystemPrincipal()) {
6071 return GetInfoForChrome();
6074 if (aPrincipal->GetIsNullPrincipal()) {
6075 NS_WARNING("IndexedDB not supported from this principal!");
6076 return Err(NS_ERROR_FAILURE);
6079 PrincipalMetadata principalMetadata;
6081 QM_TRY(MOZ_TO_RESULT(aPrincipal->GetOrigin(principalMetadata.mOrigin)));
6083 if (principalMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
6084 NS_WARNING("Non-chrome principal can't use chrome origin!");
6085 return Err(NS_ERROR_FAILURE);
6088 aPrincipal->OriginAttributesRef().CreateSuffix(principalMetadata.mSuffix);
6090 nsAutoCString baseDomain;
6091 QM_TRY(MOZ_TO_RESULT(aPrincipal->GetBaseDomain(baseDomain)));
6093 MOZ_ASSERT(!baseDomain.IsEmpty());
6095 principalMetadata.mGroup = baseDomain + principalMetadata.mSuffix;
6097 principalMetadata.mStorageOrigin = principalMetadata.mOrigin;
6099 principalMetadata.mIsPrivate = aPrincipal->GetPrivateBrowsingId() != 0;
6101 return principalMetadata;
6104 Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromWindow(
6105 nsPIDOMWindowOuter* aWindow) {
6106 MOZ_ASSERT(NS_IsMainThread());
6107 MOZ_ASSERT(aWindow);
6109 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
6110 QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
6112 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
6113 QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
6115 return GetInfoFromPrincipal(principal);
6118 // static
6119 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromPrincipal(
6120 nsIPrincipal* aPrincipal) {
6121 MOZ_ASSERT(NS_IsMainThread());
6122 MOZ_ASSERT(aPrincipal);
6124 if (aPrincipal->IsSystemPrincipal()) {
6125 return nsAutoCString{GetOriginForChrome()};
6128 if (aPrincipal->GetIsNullPrincipal()) {
6129 NS_WARNING("IndexedDB not supported from this principal!");
6130 return Err(NS_ERROR_FAILURE);
6133 QM_TRY_UNWRAP(const auto origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6134 nsAutoCString, aPrincipal, GetOrigin));
6136 if (origin.EqualsLiteral(kChromeOrigin)) {
6137 NS_WARNING("Non-chrome principal can't use chrome origin!");
6138 return Err(NS_ERROR_FAILURE);
6141 return origin;
6144 // static
6145 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromWindow(
6146 nsPIDOMWindowOuter* aWindow) {
6147 MOZ_ASSERT(NS_IsMainThread());
6148 MOZ_ASSERT(aWindow);
6150 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
6151 QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
6153 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
6154 QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
6156 QM_TRY_RETURN(GetOriginFromPrincipal(principal));
6159 // static
6160 PrincipalMetadata QuotaManager::GetInfoForChrome() {
6161 return {{},
6162 GetOriginForChrome(),
6163 GetOriginForChrome(),
6164 GetOriginForChrome(),
6165 false};
6168 // static
6169 nsLiteralCString QuotaManager::GetOriginForChrome() {
6170 return nsLiteralCString{kChromeOrigin};
6173 // static
6174 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
6175 MOZ_ASSERT(!aOrigin.IsEmpty());
6177 // The first prompt is not required for these origins.
6178 if (aOrigin.EqualsLiteral(kChromeOrigin) ||
6179 StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
6180 StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
6181 StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
6182 return true;
6185 return false;
6188 // static
6189 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1,
6190 const nsACString& aOrigin2) {
6191 return MakeSanitizedOriginCString(aOrigin1) ==
6192 MakeSanitizedOriginCString(aOrigin2);
6195 // static
6196 Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin(
6197 const nsACString& aOrigin) {
6198 // An origin string either corresponds to a SystemPrincipalInfo or a
6199 // ContentPrincipalInfo, see
6200 // QuotaManager::GetOriginFromValidatedPrincipalInfo.
6202 nsCString spec;
6203 OriginAttributes attrs;
6204 nsCString originalSuffix;
6205 const OriginParser::ResultType result = OriginParser::ParseOrigin(
6206 MakeSanitizedOriginCString(aOrigin), spec, &attrs, originalSuffix);
6207 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
6209 QM_TRY_INSPECT(
6210 const auto& principal,
6211 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
6212 if (spec.EqualsLiteral(kChromeOrigin)) {
6213 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
6216 nsCOMPtr<nsIURI> uri;
6217 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
6219 return nsCOMPtr<nsIPrincipal>(
6220 BasePrincipal::CreateContentPrincipal(uri, attrs));
6221 }()));
6222 QM_TRY(MOZ_TO_RESULT(principal));
6224 PrincipalInfo principalInfo;
6225 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
6227 return std::move(principalInfo);
6230 // static
6231 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; }
6233 uint64_t QuotaManager::LockedCollectOriginsForEviction(
6234 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
6235 mQuotaMutex.AssertCurrentThreadOwns();
6237 RefPtr<CollectOriginsHelper> helper =
6238 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
6240 // Unlock while calling out to XPCOM (code behind the dispatch method needs
6241 // to acquire its own lock which can potentially lead to a deadlock and it
6242 // also calls an observer that can do various stuff like IO, so it's better
6243 // to not hold our mutex while that happens).
6245 MutexAutoUnlock autoUnlock(mQuotaMutex);
6247 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
6250 return helper->BlockAndReturnOriginsForEviction(aLocks);
6253 void QuotaManager::LockedRemoveQuotaForRepository(
6254 PersistenceType aPersistenceType) {
6255 mQuotaMutex.AssertCurrentThreadOwns();
6256 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6258 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
6259 auto& pair = iter.Data();
6261 if (RefPtr<GroupInfo> groupInfo =
6262 pair->LockedGetGroupInfo(aPersistenceType)) {
6263 groupInfo->LockedRemoveOriginInfos();
6265 pair->LockedClearGroupInfo(aPersistenceType);
6267 if (!pair->LockedHasGroupInfos()) {
6268 iter.Remove();
6274 void QuotaManager::LockedRemoveQuotaForOrigin(
6275 const OriginMetadata& aOriginMetadata) {
6276 mQuotaMutex.AssertCurrentThreadOwns();
6277 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6279 GroupInfoPair* pair;
6280 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6281 return;
6284 MOZ_ASSERT(pair);
6286 if (RefPtr<GroupInfo> groupInfo =
6287 pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType)) {
6288 groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin);
6290 if (!groupInfo->LockedHasOriginInfos()) {
6291 pair->LockedClearGroupInfo(aOriginMetadata.mPersistenceType);
6293 if (!pair->LockedHasGroupInfos()) {
6294 mGroupInfoPairs.Remove(aOriginMetadata.mGroup);
6300 already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
6301 PersistenceType aPersistenceType, const nsACString& aSuffix,
6302 const nsACString& aGroup) {
6303 mQuotaMutex.AssertCurrentThreadOwns();
6304 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6306 GroupInfoPair* const pair =
6307 mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup);
6309 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6310 if (!groupInfo) {
6311 groupInfo = new GroupInfo(pair, aPersistenceType);
6312 pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
6315 return groupInfo.forget();
6318 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
6319 PersistenceType aPersistenceType,
6320 const OriginMetadata& aOriginMetadata) const {
6321 mQuotaMutex.AssertCurrentThreadOwns();
6322 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6324 GroupInfoPair* pair;
6325 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6326 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6327 if (groupInfo) {
6328 return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
6332 return nullptr;
6335 template <typename Iterator>
6336 void QuotaManager::MaybeInsertNonPersistedOriginInfos(
6337 Iterator aDest, const RefPtr<GroupInfo>& aTemporaryGroupInfo,
6338 const RefPtr<GroupInfo>& aDefaultGroupInfo,
6339 const RefPtr<GroupInfo>& aPrivateGroupInfo) {
6340 const auto copy = [&aDest](const GroupInfo& groupInfo) {
6341 std::copy_if(
6342 groupInfo.mOriginInfos.cbegin(), groupInfo.mOriginInfos.cend(), aDest,
6343 [](const auto& originInfo) { return !originInfo->LockedPersisted(); });
6346 if (aTemporaryGroupInfo) {
6347 MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY ==
6348 aTemporaryGroupInfo->GetPersistenceType());
6350 copy(*aTemporaryGroupInfo);
6352 if (aDefaultGroupInfo) {
6353 MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT ==
6354 aDefaultGroupInfo->GetPersistenceType());
6356 copy(*aDefaultGroupInfo);
6358 if (aPrivateGroupInfo) {
6359 MOZ_ASSERT(PERSISTENCE_TYPE_PRIVATE ==
6360 aPrivateGroupInfo->GetPersistenceType());
6361 copy(*aPrivateGroupInfo);
6365 template <typename Collect, typename Pred>
6366 QuotaManager::OriginInfosFlatTraversable
6367 QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) {
6368 OriginInfosFlatTraversable originInfos;
6370 std::forward<Collect>(aCollect)(MakeBackInserter(originInfos));
6372 originInfos.Sort(OriginInfoAccessTimeComparator());
6374 const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(),
6375 std::forward<Pred>(aPred));
6377 originInfos.TruncateLength(foundIt - originInfos.cbegin());
6379 return originInfos;
6382 QuotaManager::OriginInfosNestedTraversable
6383 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
6384 MutexAutoLock lock(mQuotaMutex);
6386 OriginInfosNestedTraversable originInfos;
6388 for (const auto& entry : mGroupInfoPairs) {
6389 const auto& pair = entry.GetData();
6391 MOZ_ASSERT(!entry.GetKey().IsEmpty());
6392 MOZ_ASSERT(pair);
6394 uint64_t groupUsage = 0;
6396 const RefPtr<GroupInfo> temporaryGroupInfo =
6397 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
6398 if (temporaryGroupInfo) {
6399 groupUsage += temporaryGroupInfo->mUsage;
6402 const RefPtr<GroupInfo> defaultGroupInfo =
6403 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
6404 if (defaultGroupInfo) {
6405 groupUsage += defaultGroupInfo->mUsage;
6408 const RefPtr<GroupInfo> privateGroupInfo =
6409 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
6410 if (privateGroupInfo) {
6411 groupUsage += privateGroupInfo->mUsage;
6414 if (groupUsage > 0) {
6415 QuotaManager* quotaManager = QuotaManager::Get();
6416 MOZ_ASSERT(quotaManager, "Shouldn't be null!");
6418 if (groupUsage > quotaManager->GetGroupLimit()) {
6419 originInfos.AppendElement(CollectLRUOriginInfosUntil(
6420 [&temporaryGroupInfo, &defaultGroupInfo,
6421 &privateGroupInfo](auto inserter) {
6422 MaybeInsertNonPersistedOriginInfos(
6423 std::move(inserter), temporaryGroupInfo, defaultGroupInfo,
6424 privateGroupInfo);
6426 [&groupUsage, quotaManager](const auto& originInfo) {
6427 groupUsage -= originInfo->LockedUsage();
6429 return groupUsage <= quotaManager->GetGroupLimit();
6430 }));
6435 return originInfos;
6438 QuotaManager::OriginInfosNestedTraversable
6439 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
6440 MutexAutoLock lock(mQuotaMutex);
6442 QuotaManager::OriginInfosNestedTraversable res;
6443 res.AppendElement(CollectLRUOriginInfosUntil(
6444 // XXX The lambda only needs to capture this, but due to Bug 1421435 it
6445 // can't.
6446 [&](auto inserter) {
6447 for (const auto& entry : mGroupInfoPairs) {
6448 const auto& pair = entry.GetData();
6450 MOZ_ASSERT(!entry.GetKey().IsEmpty());
6451 MOZ_ASSERT(pair);
6453 MaybeInsertNonPersistedOriginInfos(
6454 inserter, pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY),
6455 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT),
6456 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE));
6459 [temporaryStorageUsage = mTemporaryStorageUsage,
6460 temporaryStorageLimit = mTemporaryStorageLimit,
6461 doomedUsage = uint64_t{0}](const auto& originInfo) mutable {
6462 if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) {
6463 return true;
6466 doomedUsage += originInfo->LockedUsage();
6467 return false;
6468 }));
6470 return res;
6473 void QuotaManager::ClearOrigins(
6474 const OriginInfosNestedTraversable& aDoomedOriginInfos) {
6475 AssertIsOnIOThread();
6477 // If we are in shutdown, we could break off early from clearing origins.
6478 // In such cases, we would like to track the ones that were already cleared
6479 // up, such that other essential cleanup could be performed on clearedOrigins.
6480 // clearedOrigins is used in calls to LockedRemoveQuotaForOrigin and
6481 // OriginClearCompleted below. We could have used a collection of OriginInfos
6482 // rather than flattening them to OriginMetadata but groupInfo in OriginInfo
6483 // is just a raw ptr and LockedRemoveQuotaForOrigin might delete groupInfo and
6484 // as a result, we would not be able to get origin persistence type required
6485 // in OriginClearCompleted call after lockedRemoveQuotaForOrigin call.
6486 nsTArray<OriginMetadata> clearedOrigins;
6488 // XXX Does this need to be done a) in order and/or b) sequentially?
6489 for (const auto& doomedOriginInfo :
6490 Flatten<OriginInfosFlatTraversable::value_type>(aDoomedOriginInfos)) {
6491 #ifdef DEBUG
6493 MutexAutoLock lock(mQuotaMutex);
6494 MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
6496 #endif
6498 // TODO: We are currently only checking for this flag here which
6499 // means that we cannot break off once we start cleaning an origin. It
6500 // could be better if we could check for shutdown flag while cleaning an
6501 // origin such that we could break off early from the cleaning process if
6502 // we are stuck cleaning on one huge origin. Bug1797098 has been filed to
6503 // track this.
6504 if (QuotaManager::IsShuttingDown()) {
6505 break;
6508 auto originMetadata = doomedOriginInfo->FlattenToOriginMetadata();
6510 DeleteOriginDirectory(originMetadata);
6512 clearedOrigins.AppendElement(std::move(originMetadata));
6516 MutexAutoLock lock(mQuotaMutex);
6518 for (const auto& clearedOrigin : clearedOrigins) {
6519 LockedRemoveQuotaForOrigin(clearedOrigin);
6523 for (const auto& clearedOrigin : clearedOrigins) {
6524 OriginClearCompleted(clearedOrigin.mPersistenceType, clearedOrigin.mOrigin,
6525 Nullable<Client::Type>());
6529 void QuotaManager::CleanupTemporaryStorage() {
6530 AssertIsOnIOThread();
6532 // Evicting origins that exceed their group limit also affects the global
6533 // temporary storage usage, so these steps have to be taken sequentially.
6534 // Combining them doesn't seem worth the added complexity.
6535 ClearOrigins(GetOriginInfosExceedingGroupLimit());
6536 ClearOrigins(GetOriginInfosExceedingGlobalLimit());
6538 if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
6539 // If disk space is still low after origin clear, notify storage pressure.
6540 NotifyStoragePressure(mTemporaryStorageUsage);
6544 void QuotaManager::DeleteOriginDirectory(
6545 const OriginMetadata& aOriginMetadata) {
6546 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
6547 QM_VOID);
6549 nsresult rv = directory->Remove(true);
6550 if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
6551 // This should never fail if we've closed all storage connections
6552 // correctly...
6553 NS_ERROR("Failed to remove directory!");
6557 void QuotaManager::FinalizeOriginEviction(
6558 nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
6559 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
6561 auto finalizeOriginEviction = [locks = std::move(aLocks)]() mutable {
6562 QuotaManager* quotaManager = QuotaManager::Get();
6563 MOZ_ASSERT(quotaManager);
6565 RefPtr<OriginOperationBase> op = CreateFinalizeOriginEvictionOp(
6566 WrapMovingNotNull(quotaManager), std::move(locks));
6568 op->RunImmediately();
6571 if (IsOnBackgroundThread()) {
6572 finalizeOriginEviction();
6573 } else {
6574 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(
6575 NS_NewRunnableFunction(
6576 "dom::quota::QuotaManager::FinalizeOriginEviction",
6577 std::move(finalizeOriginEviction)),
6578 NS_DISPATCH_NORMAL));
6582 Result<Ok, nsresult> QuotaManager::ArchiveOrigins(
6583 const nsTArray<FullOriginMetadata>& aFullOriginMetadatas) {
6584 AssertIsOnIOThread();
6585 MOZ_ASSERT(!aFullOriginMetadatas.IsEmpty());
6587 QM_TRY_INSPECT(const auto& storageArchivesDir,
6588 QM_NewLocalFile(*mStorageArchivesPath));
6590 // Create another subdir, so once we decide to remove all temporary archives,
6591 // we can remove only the subdir and the parent directory can still be used
6592 // for something else or similar in future. Otherwise, we would have to
6593 // figure out a new name for it.
6594 QM_TRY(MOZ_TO_RESULT(storageArchivesDir->Append(u"0"_ns)));
6596 PRExplodedTime now;
6597 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
6599 const auto dateStr =
6600 nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32, now.tm_year,
6601 now.tm_month + 1, now.tm_mday);
6603 QM_TRY_INSPECT(
6604 const auto& storageArchiveDir,
6605 CloneFileAndAppend(*storageArchivesDir, NS_ConvertASCIItoUTF16(dateStr)));
6607 QM_TRY(MOZ_TO_RESULT(
6608 storageArchiveDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700)));
6610 QM_TRY_INSPECT(const auto& defaultStorageArchiveDir,
6611 CloneFileAndAppend(*storageArchiveDir,
6612 nsLiteralString(DEFAULT_DIRECTORY_NAME)));
6614 QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir,
6615 CloneFileAndAppend(*storageArchiveDir,
6616 nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
6618 for (const auto& fullOriginMetadata : aFullOriginMetadatas) {
6619 MOZ_ASSERT(
6620 IsBestEffortPersistenceType(fullOriginMetadata.mPersistenceType));
6622 QM_TRY_INSPECT(const auto& directory,
6623 GetOriginDirectory(fullOriginMetadata));
6625 // The origin could have been removed, for example due to corruption.
6626 QM_TRY_INSPECT(
6627 const auto& moved,
6628 QM_OR_ELSE_WARN_IF(
6629 // Expression.
6630 MOZ_TO_RESULT(
6631 directory->MoveTo(fullOriginMetadata.mPersistenceType ==
6632 PERSISTENCE_TYPE_DEFAULT
6633 ? defaultStorageArchiveDir
6634 : temporaryStorageArchiveDir,
6635 u""_ns))
6636 .map([](Ok) { return true; }),
6637 // Predicate.
6638 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
6639 // Fallback.
6640 ErrToOk<false>));
6642 if (moved) {
6643 RemoveQuotaForOrigin(fullOriginMetadata.mPersistenceType,
6644 fullOriginMetadata);
6648 return Ok{};
6651 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
6652 -> DirectoryLockTable& {
6653 switch (aPersistenceType) {
6654 case PERSISTENCE_TYPE_TEMPORARY:
6655 return mTemporaryDirectoryLockTable;
6656 case PERSISTENCE_TYPE_DEFAULT:
6657 return mDefaultDirectoryLockTable;
6658 case PERSISTENCE_TYPE_PRIVATE:
6659 return mPrivateDirectoryLockTable;
6661 case PERSISTENCE_TYPE_PERSISTENT:
6662 case PERSISTENCE_TYPE_INVALID:
6663 default:
6664 MOZ_CRASH("Bad persistence type value!");
6668 void QuotaManager::ClearDirectoryLockTables() {
6669 AssertIsOnOwningThread();
6671 for (const PersistenceType type : kBestEffortPersistenceTypes) {
6672 DirectoryLockTable& directoryLockTable = GetDirectoryLockTable(type);
6674 if (!IsShuttingDown()) {
6675 for (const auto& entry : directoryLockTable) {
6676 const auto& array = entry.GetData();
6678 // It doesn't matter which lock is used, they all have the same
6679 // persistence type and origin metadata.
6680 MOZ_ASSERT(!array->IsEmpty());
6681 const auto& lock = array->ElementAt(0);
6683 UpdateOriginAccessTime(lock->GetPersistenceType(),
6684 lock->OriginMetadata());
6688 directoryLockTable.Clear();
6692 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
6693 AssertIsOnIOThread();
6695 // Do not parse this sanitized origin string, if we already parsed it.
6696 return mValidOrigins.LookupOrInsertWith(
6697 aSanitizedOrigin, [&aSanitizedOrigin] {
6698 nsCString spec;
6699 OriginAttributes attrs;
6700 nsCString originalSuffix;
6701 const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec,
6702 &attrs, originalSuffix);
6704 return result == OriginParser::ValidOrigin;
6708 Result<nsCString, nsresult> QuotaManager::EnsureStorageOriginFromOrigin(
6709 const nsACString& aOrigin) {
6710 MutexAutoLock lock(mQuotaMutex);
6712 QM_TRY_UNWRAP(
6713 auto storageOrigin,
6714 mOriginToStorageOriginMap.TryLookupOrInsertWith(
6715 aOrigin, [this, &aOrigin]() -> Result<nsCString, nsresult> {
6716 OriginAttributes originAttributes;
6718 nsCString originNoSuffix;
6719 QM_TRY(MOZ_TO_RESULT(
6720 originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix)));
6722 nsCOMPtr<nsIURI> uri;
6723 QM_TRY(MOZ_TO_RESULT(
6724 NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
6725 .SetSpec(originNoSuffix)
6726 .SetScheme(kUUIDOriginScheme)
6727 .SetHost(NSID_TrimBracketsASCII(nsID::GenerateUUID()))
6728 .SetPort(-1)
6729 .Finalize(uri)));
6731 nsCOMPtr<nsIPrincipal> principal =
6732 BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{});
6733 QM_TRY(MOZ_TO_RESULT(principal));
6735 QM_TRY_UNWRAP(auto origin,
6736 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6737 nsAutoCString, principal, GetOrigin));
6739 mStorageOriginToOriginMap.WithEntryHandle(
6740 origin,
6741 [&aOrigin](auto entryHandle) { entryHandle.Insert(aOrigin); });
6743 return nsCString(std::move(origin));
6744 }));
6746 return nsCString(std::move(storageOrigin));
6749 Result<nsCString, nsresult> QuotaManager::GetOriginFromStorageOrigin(
6750 const nsACString& aStorageOrigin) {
6751 MutexAutoLock lock(mQuotaMutex);
6753 auto maybeOrigin = mStorageOriginToOriginMap.MaybeGet(aStorageOrigin);
6754 if (maybeOrigin.isNothing()) {
6755 return Err(NS_ERROR_FAILURE);
6758 return maybeOrigin.ref();
6761 int64_t QuotaManager::GenerateDirectoryLockId() {
6762 const int64_t directorylockId = mNextDirectoryLockId;
6764 if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1;
6765 result.isValid()) {
6766 mNextDirectoryLockId = result.value();
6767 } else {
6768 NS_WARNING("Quota manager has run out of ids for directory locks!");
6770 // There's very little chance for this to happen given the max size of
6771 // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
6772 // to zero since such old directory locks shouldn't exist anymore.
6773 mNextDirectoryLockId = 0;
6776 // TODO: Maybe add an assertion here to check that there is no existing
6777 // directory lock with given id.
6779 return directorylockId;
6782 template <typename Func>
6783 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
6784 Func&& aFunc)
6785 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
6786 Initialization, StringGenerator>&> {
6787 return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
6788 std::forward<Func>(aFunc));
6791 template <typename Func>
6792 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
6793 const nsACString& aContext,
6794 Func&& aFunc)
6795 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
6796 Initialization, StringGenerator>&> {
6797 return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
6798 aContext, std::forward<Func>(aFunc));
6801 template <typename Func>
6802 auto QuotaManager::ExecuteOriginInitialization(
6803 const nsACString& aOrigin, const OriginInitialization aInitialization,
6804 const nsACString& aContext, Func&& aFunc)
6805 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
6806 Initialization, StringGenerator>&> {
6807 return quota::ExecuteInitialization(
6808 mInitializationInfo.MutableOriginInitializationInfoRef(
6809 aOrigin, CreateIfNonExistent{}),
6810 aInitialization, aContext, std::forward<Func>(aFunc));
6813 /*******************************************************************************
6814 * Local class implementations
6815 ******************************************************************************/
6817 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
6818 uint64_t aMinSizeToBeFreed)
6819 : Runnable("dom::quota::CollectOriginsHelper"),
6820 mMinSizeToBeFreed(aMinSizeToBeFreed),
6821 mMutex(aMutex),
6822 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
6823 mSizeToBeFreed(0),
6824 mWaiting(true) {
6825 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
6826 mMutex.AssertCurrentThreadOwns();
6829 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
6830 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
6831 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
6832 mMutex.AssertCurrentThreadOwns();
6834 while (mWaiting) {
6835 mCondVar.Wait();
6838 mLocks.SwapElements(aLocks);
6839 return mSizeToBeFreed;
6842 NS_IMETHODIMP
6843 CollectOriginsHelper::Run() {
6844 AssertIsOnBackgroundThread();
6846 QuotaManager* quotaManager = QuotaManager::Get();
6847 NS_ASSERTION(quotaManager, "Shouldn't be null!");
6849 // We use extra stack vars here to avoid race detector warnings (the same
6850 // memory accessed with and without the lock held).
6851 nsTArray<RefPtr<OriginDirectoryLock>> locks;
6852 uint64_t sizeToBeFreed =
6853 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
6855 MutexAutoLock lock(mMutex);
6857 NS_ASSERTION(mWaiting, "Huh?!");
6859 mLocks.SwapElements(locks);
6860 mSizeToBeFreed = sizeToBeFreed;
6861 mWaiting = false;
6862 mCondVar.Notify();
6864 return NS_OK;
6867 NS_IMETHODIMP
6868 StoragePressureRunnable::Run() {
6869 MOZ_ASSERT(NS_IsMainThread());
6871 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
6872 if (NS_WARN_IF(!obsSvc)) {
6873 return NS_ERROR_FAILURE;
6876 nsCOMPtr<nsISupportsPRUint64> wrapper =
6877 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
6878 if (NS_WARN_IF(!wrapper)) {
6879 return NS_ERROR_FAILURE;
6882 wrapper->SetData(mUsage);
6884 obsSvc->NotifyObservers(wrapper, "QuotaManager::StoragePressure", u"");
6886 return NS_OK;
6889 TimeStamp RecordTimeDeltaHelper::Start() {
6890 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
6892 // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
6893 // gLastOSWake is set, then this time duration would still be recorded with
6894 // key "Normal". We are assumming this is rather rare to happen.
6895 mStartTime.init(TimeStamp::Now());
6896 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
6898 return *mStartTime;
6901 TimeStamp RecordTimeDeltaHelper::End() {
6902 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
6904 mEndTime.init(TimeStamp::Now());
6905 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
6907 return *mEndTime;
6910 NS_IMETHODIMP
6911 RecordTimeDeltaHelper::Run() {
6912 MOZ_ASSERT(NS_IsMainThread());
6914 if (mInitializedTime.isSome()) {
6915 // Keys for QM_QUOTA_INFO_LOAD_TIME_V0 and QM_SHUTDOWN_TIME_V0:
6916 // Normal: Normal conditions.
6917 // WasSuspended: There was a OS sleep so that it was suspended.
6918 // TimeStampErr1: The recorded start time is unexpectedly greater than the
6919 // end time.
6920 // TimeStampErr2: The initialized time for the recording class is unexpectly
6921 // greater than the last OS wake time.
6922 const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() {
6923 if (wasSuspended) {
6924 return "WasSuspended"_ns;
6927 // XXX File a bug if we have data for this key.
6928 // We found negative values in our query in STMO for
6929 // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
6930 // because the documentation for TimeStamp::Now() says it returns a
6931 // monotonically increasing number.
6932 if (*mStartTime > *mEndTime) {
6933 return "TimeStampErr1"_ns;
6936 if (*mInitializedTime > gLastOSWake) {
6937 return "TimeStampErr2"_ns;
6940 return "Normal"_ns;
6941 }();
6943 Telemetry::AccumulateTimeDelta(mHistogram, key, *mStartTime, *mEndTime);
6945 return NS_OK;
6948 gLastOSWake = TimeStamp::Now();
6949 mInitializedTime.init(gLastOSWake);
6951 return NS_OK;
6954 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
6955 int64_t& aTimestamp,
6956 nsACString& aGroup,
6957 nsACString& aOrigin,
6958 Nullable<bool>& aIsApp) {
6959 AssertIsOnIOThread();
6960 MOZ_ASSERT(aDirectory);
6962 QM_TRY_INSPECT(
6963 const auto& binaryStream,
6964 GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
6966 QM_TRY_INSPECT(const uint64_t& timestamp,
6967 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
6969 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6970 nsCString, binaryStream, ReadCString));
6972 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6973 nsCString, binaryStream, ReadCString));
6975 Nullable<bool> isApp;
6976 bool value;
6977 if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
6978 isApp.SetValue(value);
6981 aTimestamp = timestamp;
6982 aGroup = group;
6983 aOrigin = origin;
6984 aIsApp = std::move(isApp);
6985 return NS_OK;
6988 nsresult StorageOperationBase::GetDirectoryMetadata2(
6989 nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
6990 nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
6991 AssertIsOnIOThread();
6992 MOZ_ASSERT(aDirectory);
6994 QM_TRY_INSPECT(const auto& binaryStream,
6995 GetBinaryInputStream(*aDirectory,
6996 nsLiteralString(METADATA_V2_FILE_NAME)));
6998 QM_TRY_INSPECT(const uint64_t& timestamp,
6999 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
7001 QM_TRY_INSPECT(const bool& persisted,
7002 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
7003 Unused << persisted;
7005 QM_TRY_INSPECT(const bool& reservedData1,
7006 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
7007 Unused << reservedData1;
7009 QM_TRY_INSPECT(const bool& reservedData2,
7010 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
7011 Unused << reservedData2;
7013 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7014 nsCString, binaryStream, ReadCString));
7016 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7017 nsCString, binaryStream, ReadCString));
7019 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7020 nsCString, binaryStream, ReadCString));
7022 QM_TRY_INSPECT(const bool& isApp,
7023 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
7025 aTimestamp = timestamp;
7026 aSuffix = suffix;
7027 aGroup = group;
7028 aOrigin = origin;
7029 aIsApp = isApp;
7030 return NS_OK;
7033 int64_t StorageOperationBase::GetOriginLastModifiedTime(
7034 const OriginProps& aOriginProps) {
7035 return GetLastModifiedTime(*aOriginProps.mPersistenceType,
7036 *aOriginProps.mDirectory);
7039 nsresult StorageOperationBase::RemoveObsoleteOrigin(
7040 const OriginProps& aOriginProps) {
7041 AssertIsOnIOThread();
7043 QM_WARNING(
7044 "Deleting obsolete %s directory that is no longer a legal "
7045 "origin!",
7046 NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
7048 QM_TRY(MOZ_TO_RESULT(aOriginProps.mDirectory->Remove(/* recursive */ true)));
7050 return NS_OK;
7053 Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin(
7054 const OriginProps& aOriginProps) {
7055 AssertIsOnIOThread();
7057 const nsAString& oldLeafName = aOriginProps.mLeafName;
7059 const auto newLeafName =
7060 MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin);
7062 if (oldLeafName == newLeafName) {
7063 return false;
7066 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7067 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7068 aOriginProps.mOriginMetadata)));
7070 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7071 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7072 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7074 QM_TRY_INSPECT(const auto& newFile,
7075 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7076 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent));
7078 QM_TRY(MOZ_TO_RESULT(newFile->Append(newLeafName)));
7080 QM_TRY_INSPECT(const bool& exists,
7081 MOZ_TO_RESULT_INVOKE_MEMBER(newFile, Exists));
7083 if (exists) {
7084 QM_WARNING(
7085 "Can't rename %s directory to %s, the target already exists, removing "
7086 "instead of renaming!",
7087 NS_ConvertUTF16toUTF8(oldLeafName).get(),
7088 NS_ConvertUTF16toUTF8(newLeafName).get());
7091 QM_TRY(CallWithDelayedRetriesIfAccessDenied(
7092 [&exists, &aOriginProps, &newLeafName] {
7093 if (exists) {
7094 QM_TRY_RETURN(MOZ_TO_RESULT(
7095 aOriginProps.mDirectory->Remove(/* recursive */ true)));
7097 QM_TRY_RETURN(MOZ_TO_RESULT(
7098 aOriginProps.mDirectory->RenameTo(nullptr, newLeafName)));
7100 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
7101 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
7103 return true;
7106 nsresult StorageOperationBase::ProcessOriginDirectories() {
7107 AssertIsOnIOThread();
7108 MOZ_ASSERT(!mOriginProps.IsEmpty());
7110 QuotaManager* quotaManager = QuotaManager::Get();
7111 MOZ_ASSERT(quotaManager);
7113 for (auto& originProps : mOriginProps) {
7114 switch (originProps.mType) {
7115 case OriginProps::eChrome: {
7116 originProps.mOriginMetadata = {QuotaManager::GetInfoForChrome(),
7117 *originProps.mPersistenceType};
7118 break;
7121 case OriginProps::eContent: {
7122 nsCOMPtr<nsIURI> uri;
7123 QM_TRY(
7124 MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), originProps.mSpec)));
7126 nsCOMPtr<nsIPrincipal> principal =
7127 BasePrincipal::CreateContentPrincipal(uri, originProps.mAttrs);
7128 QM_TRY(MOZ_TO_RESULT(principal));
7130 PrincipalInfo principalInfo;
7131 QM_TRY(
7132 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
7134 QM_WARNONLY_TRY_UNWRAP(
7135 auto valid,
7136 MOZ_TO_RESULT(quotaManager->IsPrincipalInfoValid(principalInfo)));
7138 if (!valid) {
7139 // Unknown directories during upgrade are allowed. Just warn if we
7140 // find them.
7141 UNKNOWN_FILE_WARNING(originProps.mLeafName);
7142 originProps.mIgnore = true;
7143 break;
7146 QM_TRY_UNWRAP(
7147 auto principalMetadata,
7148 quotaManager->GetInfoFromValidatedPrincipalInfo(principalInfo));
7150 originProps.mOriginMetadata = {std::move(principalMetadata),
7151 *originProps.mPersistenceType};
7153 break;
7156 case OriginProps::eObsolete: {
7157 // There's no way to get info for obsolete origins.
7158 break;
7161 default:
7162 MOZ_CRASH("Bad type!");
7166 // Don't try to upgrade obsolete origins, remove them right after we detect
7167 // them.
7168 for (const auto& originProps : mOriginProps) {
7169 if (originProps.mType == OriginProps::eObsolete) {
7170 MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty());
7171 MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty());
7172 MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty());
7174 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps)));
7175 } else if (!originProps.mIgnore) {
7176 MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty());
7177 MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty());
7179 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps)));
7183 return NS_OK;
7186 // XXX Do the fallible initialization in a separate non-static member function
7187 // of StorageOperationBase and eventually get rid of this method and use a
7188 // normal constructor instead.
7189 template <typename PersistenceTypeFunc>
7190 nsresult StorageOperationBase::OriginProps::Init(
7191 PersistenceTypeFunc&& aPersistenceTypeFunc) {
7192 AssertIsOnIOThread();
7194 QM_TRY_INSPECT(const auto& leafName,
7195 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, *mDirectory,
7196 GetLeafName));
7198 // XXX Consider using QuotaManager::ParseOrigin here.
7199 nsCString spec;
7200 OriginAttributes attrs;
7201 nsCString originalSuffix;
7202 OriginParser::ResultType result = OriginParser::ParseOrigin(
7203 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
7204 if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
7205 mType = OriginProps::eInvalid;
7206 return NS_OK;
7209 const auto persistenceType = [&]() -> PersistenceType {
7210 // XXX We shouldn't continue with initialization if OriginParser returned
7211 // anything else but ValidOrigin. Otherwise, we have to deal with empty
7212 // spec when the origin is obsolete, like here. The caller should handle
7213 // the errors. Until it's fixed, we have to treat obsolete origins as
7214 // origins with unknown/invalid persistence type.
7215 if (result != OriginParser::ValidOrigin) {
7216 return PERSISTENCE_TYPE_INVALID;
7218 return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec);
7219 }();
7221 mLeafName = leafName;
7222 mSpec = spec;
7223 mAttrs = attrs;
7224 mOriginalSuffix = originalSuffix;
7225 mPersistenceType.init(persistenceType);
7226 if (result == OriginParser::ObsoleteOrigin) {
7227 mType = eObsolete;
7228 } else if (mSpec.EqualsLiteral(kChromeOrigin)) {
7229 mType = eChrome;
7230 } else {
7231 mType = eContent;
7234 return NS_OK;
7237 nsresult RepositoryOperationBase::ProcessRepository() {
7238 AssertIsOnIOThread();
7240 #ifdef DEBUG
7242 QM_TRY_INSPECT(const bool& exists,
7243 MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory, Exists),
7244 QM_ASSERT_UNREACHABLE);
7245 MOZ_ASSERT(exists);
7247 #endif
7249 QM_TRY(CollectEachFileEntry(
7250 *mDirectory,
7251 [](const auto& originFile) -> Result<mozilla::Ok, nsresult> {
7252 QM_TRY_INSPECT(const auto& leafName,
7253 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7254 nsAutoString, originFile, GetLeafName));
7256 // Unknown files during upgrade are allowed. Just warn if we find
7257 // them.
7258 if (!IsOSMetadata(leafName)) {
7259 UNKNOWN_FILE_WARNING(leafName);
7262 return mozilla::Ok{};
7264 [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> {
7265 OriginProps originProps(WrapMovingNotNullUnchecked(originDir));
7266 QM_TRY(MOZ_TO_RESULT(originProps.Init([&self](const auto& aSpec) {
7267 return self.PersistenceTypeFromSpec(aSpec);
7268 })));
7269 // Bypass invalid origins while upgrading
7270 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{});
7272 if (originProps.mType != OriginProps::eObsolete) {
7273 QM_TRY_INSPECT(const bool& removed,
7274 MOZ_TO_RESULT_INVOKE_MEMBER(
7275 self, PrepareOriginDirectory, originProps));
7276 if (removed) {
7277 return mozilla::Ok{};
7281 self.mOriginProps.AppendElement(std::move(originProps));
7283 return mozilla::Ok{};
7284 }));
7286 if (mOriginProps.IsEmpty()) {
7287 return NS_OK;
7290 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
7292 return NS_OK;
7295 template <typename UpgradeMethod>
7296 nsresult RepositoryOperationBase::MaybeUpgradeClients(
7297 const OriginProps& aOriginProps, UpgradeMethod aMethod) {
7298 AssertIsOnIOThread();
7299 MOZ_ASSERT(aMethod);
7301 QuotaManager* quotaManager = QuotaManager::Get();
7302 MOZ_ASSERT(quotaManager);
7304 QM_TRY(CollectEachFileEntry(
7305 *aOriginProps.mDirectory,
7306 [](const auto& file) -> Result<mozilla::Ok, nsresult> {
7307 QM_TRY_INSPECT(
7308 const auto& leafName,
7309 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName));
7311 if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
7312 UNKNOWN_FILE_WARNING(leafName);
7315 return mozilla::Ok{};
7317 [quotaManager, &aMethod,
7318 &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> {
7319 QM_TRY_INSPECT(
7320 const auto& leafName,
7321 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, dir, GetLeafName));
7323 QM_TRY_INSPECT(const bool& removed,
7324 MOZ_TO_RESULT_INVOKE_MEMBER(self, PrepareClientDirectory,
7325 dir, leafName));
7326 if (removed) {
7327 return mozilla::Ok{};
7330 Client::Type clientType;
7331 bool ok = Client::TypeFromText(leafName, clientType, fallible);
7332 if (!ok) {
7333 UNKNOWN_FILE_WARNING(leafName);
7334 return mozilla::Ok{};
7337 Client* client = quotaManager->GetClient(clientType);
7338 MOZ_ASSERT(client);
7340 QM_TRY(MOZ_TO_RESULT((client->*aMethod)(dir)));
7342 return mozilla::Ok{};
7343 }));
7345 return NS_OK;
7348 nsresult RepositoryOperationBase::PrepareClientDirectory(
7349 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
7350 AssertIsOnIOThread();
7352 aRemoved = false;
7353 return NS_OK;
7356 nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() {
7357 AssertIsOnIOThread();
7358 MOZ_ASSERT(mDirectory);
7360 const auto maybeLegacyPersistenceType =
7361 LegacyPersistenceTypeFromFile(*mDirectory, fallible);
7362 QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE));
7364 mLegacyPersistenceType.init(maybeLegacyPersistenceType.value());
7366 return NS_OK;
7369 Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType>
7370 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
7371 nsIFile& aFile, const fallible_t&) {
7372 nsAutoString leafName;
7373 MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName));
7375 if (leafName.Equals(u"persistent"_ns)) {
7376 return Some(LegacyPersistenceType::Persistent);
7379 if (leafName.Equals(u"temporary"_ns)) {
7380 return Some(LegacyPersistenceType::Temporary);
7383 return Nothing();
7386 PersistenceType
7387 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
7388 const nsCString& aSpec) {
7389 if (QuotaManager::IsOriginInternal(aSpec)) {
7390 return PERSISTENCE_TYPE_PERSISTENT;
7393 return PERSISTENCE_TYPE_DEFAULT;
7396 PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
7397 const nsCString& aSpec) {
7398 switch (*mLegacyPersistenceType) {
7399 case LegacyPersistenceType::Persistent:
7400 return PersistenceTypeFromLegacyPersistentSpec(aSpec);
7401 case LegacyPersistenceType::Temporary:
7402 return PERSISTENCE_TYPE_TEMPORARY;
7404 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
7407 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
7408 nsIFile* aDirectory) {
7409 AssertIsOnIOThread();
7410 MOZ_ASSERT(aDirectory);
7412 QM_TRY_INSPECT(
7413 const auto& metadataFile,
7414 CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
7416 QM_TRY_INSPECT(const bool& exists,
7417 MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile, Exists));
7419 if (!exists) {
7420 // Directory structure upgrade needed.
7421 // Move all files to IDB specific directory.
7423 nsString idbDirectoryName;
7424 QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)),
7425 NS_ERROR_FAILURE);
7427 QM_TRY_INSPECT(const auto& idbDirectory,
7428 CloneFileAndAppend(*aDirectory, idbDirectoryName));
7430 // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF
7431 // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the
7432 // idb directory shouldn't exist during the upgrade and the upgrade runs
7433 // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok
7434 // here.
7435 QM_TRY(QM_OR_ELSE_WARN_IF(
7436 // Expression.
7437 MOZ_TO_RESULT(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
7438 // Predicate.
7439 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
7440 // Fallback.
7441 ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> {
7442 QM_TRY_INSPECT(
7443 const bool& isDirectory,
7444 MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory, IsDirectory));
7446 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
7448 return Ok{};
7449 })));
7451 QM_TRY(CollectEachFile(
7452 *aDirectory,
7453 [&idbDirectory, &idbDirectoryName](
7454 const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
7455 QM_TRY_INSPECT(const auto& leafName,
7456 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
7457 GetLeafName));
7459 if (!leafName.Equals(idbDirectoryName)) {
7460 QM_TRY(MOZ_TO_RESULT(file->MoveTo(idbDirectory, u""_ns)));
7463 return Ok{};
7464 }));
7466 QM_TRY(
7467 MOZ_TO_RESULT(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
7470 return NS_OK;
7473 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
7474 OriginProps& aOriginProps, bool* aRemoved) {
7475 AssertIsOnIOThread();
7476 MOZ_ASSERT(aRemoved);
7478 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
7479 QM_TRY(MOZ_TO_RESULT(
7480 MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get())));
7482 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7483 } else {
7484 int64_t timestamp;
7485 nsCString group;
7486 nsCString origin;
7487 Nullable<bool> isApp;
7489 QM_WARNONLY_TRY_UNWRAP(
7490 const auto maybeDirectoryMetadata,
7491 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7492 timestamp, group, origin, isApp)));
7493 if (!maybeDirectoryMetadata) {
7494 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7495 aOriginProps.mNeedsRestore = true;
7496 } else if (!isApp.IsNull()) {
7497 aOriginProps.mIgnore = true;
7501 *aRemoved = false;
7502 return NS_OK;
7505 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
7506 const OriginProps& aOriginProps) {
7507 AssertIsOnIOThread();
7509 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
7510 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7511 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7512 aOriginProps.mOriginMetadata)));
7514 // Move internal origins to new persistent storage.
7515 if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) ==
7516 PERSISTENCE_TYPE_PERSISTENT) {
7517 if (!mPermanentStorageDir) {
7518 QuotaManager* quotaManager = QuotaManager::Get();
7519 MOZ_ASSERT(quotaManager);
7521 const nsString& permanentStoragePath =
7522 quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
7524 QM_TRY_UNWRAP(mPermanentStorageDir,
7525 QM_NewLocalFile(permanentStoragePath));
7528 const nsAString& leafName = aOriginProps.mLeafName;
7530 QM_TRY_INSPECT(const auto& newDirectory,
7531 CloneFileAndAppend(*mPermanentStorageDir, leafName));
7533 QM_TRY_INSPECT(const bool& exists,
7534 MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory, Exists));
7536 if (exists) {
7537 QM_WARNING("Found %s in storage/persistent and storage/permanent !",
7538 NS_ConvertUTF16toUTF8(leafName).get());
7540 QM_TRY(MOZ_TO_RESULT(
7541 aOriginProps.mDirectory->Remove(/* recursive */ true)));
7542 } else {
7543 QM_TRY(MOZ_TO_RESULT(
7544 aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns)));
7547 } else if (aOriginProps.mNeedsRestore) {
7548 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7549 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7550 aOriginProps.mOriginMetadata)));
7551 } else if (!aOriginProps.mIgnore) {
7552 QM_TRY_INSPECT(const auto& file,
7553 CloneFileAndAppend(*aOriginProps.mDirectory,
7554 nsLiteralString(METADATA_FILE_NAME)));
7556 QM_TRY_INSPECT(const auto& stream,
7557 GetBinaryOutputStream(*file, FileFlag::Append));
7559 MOZ_ASSERT(stream);
7561 // Currently unused (used to be isApp).
7562 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
7565 return NS_OK;
7568 nsresult UpgradeStorageHelperBase::Init() {
7569 AssertIsOnIOThread();
7570 MOZ_ASSERT(mDirectory);
7572 const auto maybePersistenceType =
7573 PersistenceTypeFromFile(*mDirectory, fallible);
7574 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
7576 mPersistenceType.init(maybePersistenceType.value());
7578 return NS_OK;
7581 PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec(
7582 const nsCString& aSpec) {
7583 // There's no moving of origin directories between repositories like in the
7584 // CreateOrUpgradeDirectoryMetadataHelper
7585 return *mPersistenceType;
7588 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
7589 OriginProps& aOriginProps, bool* aRemoved) {
7590 AssertIsOnIOThread();
7591 MOZ_ASSERT(aRemoved);
7593 int64_t timestamp;
7594 nsCString group;
7595 nsCString origin;
7596 Nullable<bool> isApp;
7598 QM_WARNONLY_TRY_UNWRAP(
7599 const auto maybeDirectoryMetadata,
7600 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7601 timestamp, group, origin, isApp)));
7602 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7603 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7604 aOriginProps.mNeedsRestore = true;
7605 } else {
7606 aOriginProps.mTimestamp = timestamp;
7609 *aRemoved = false;
7610 return NS_OK;
7613 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
7614 const OriginProps& aOriginProps) {
7615 AssertIsOnIOThread();
7617 // This handles changes in origin string generation from nsIPrincipal,
7618 // especially the change from: appId+inMozBrowser+originNoSuffix
7619 // to: origin (with origin suffix).
7620 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
7621 if (renamed) {
7622 return NS_OK;
7625 if (aOriginProps.mNeedsRestore) {
7626 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7627 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7628 aOriginProps.mOriginMetadata)));
7631 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7632 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7633 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7635 return NS_OK;
7638 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
7639 const OriginProps& aOriginProps) {
7640 AssertIsOnIOThread();
7642 // The Cache API was creating top level morgue directories by accident for
7643 // a short time in nightly. This unfortunately prevents all storage from
7644 // working. So recover these profiles permanently by removing these corrupt
7645 // directories as part of this upgrade.
7647 QM_TRY_INSPECT(const auto& morgueDir,
7648 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7649 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, Clone));
7651 QM_TRY(MOZ_TO_RESULT(morgueDir->Append(u"morgue"_ns)));
7653 QM_TRY_INSPECT(const bool& exists,
7654 MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir, Exists));
7656 if (exists) {
7657 QM_WARNING("Deleting accidental morgue directory!");
7659 QM_TRY(MOZ_TO_RESULT(morgueDir->Remove(/* recursive */ true)));
7662 return NS_OK;
7665 Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
7666 const OriginProps& aOriginProps) {
7667 AssertIsOnIOThread();
7669 // TODO: This method was empty for some time due to accidental changes done
7670 // in bug 1320404. This led to renaming of origin directories like:
7671 // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
7672 // to:
7673 // https+++developer.cdn.mozilla.net^inBrowser=1
7674 // instead of just removing them.
7676 const nsCString& originalSuffix = aOriginProps.mOriginalSuffix;
7677 if (!originalSuffix.IsEmpty()) {
7678 MOZ_ASSERT(originalSuffix[0] == '^');
7680 if (!URLParams::Parse(
7681 Substring(originalSuffix, 1, originalSuffix.Length() - 1), true,
7682 [](const nsACString& aName, const nsACString& aValue) {
7683 if (aName.EqualsLiteral("appId")) {
7684 return false;
7686 return true;
7687 })) {
7688 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps)));
7690 return true;
7694 return false;
7697 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
7698 OriginProps& aOriginProps, bool* aRemoved) {
7699 AssertIsOnIOThread();
7700 MOZ_ASSERT(aRemoved);
7702 QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps)));
7704 QM_TRY(MOZ_TO_RESULT(
7705 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0)));
7707 QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps));
7708 if (removed) {
7709 *aRemoved = true;
7710 return NS_OK;
7713 int64_t timestamp;
7714 nsCString group;
7715 nsCString origin;
7716 Nullable<bool> isApp;
7717 QM_WARNONLY_TRY_UNWRAP(
7718 const auto maybeDirectoryMetadata,
7719 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7720 timestamp, group, origin, isApp)));
7721 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7722 aOriginProps.mNeedsRestore = true;
7725 nsCString suffix;
7726 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
7727 MOZ_TO_RESULT(GetDirectoryMetadata2(
7728 aOriginProps.mDirectory.get(), timestamp, suffix,
7729 group, origin, isApp.SetValue())));
7730 if (!maybeDirectoryMetadata2) {
7731 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7732 aOriginProps.mNeedsRestore2 = true;
7733 } else {
7734 aOriginProps.mTimestamp = timestamp;
7737 *aRemoved = false;
7738 return NS_OK;
7741 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
7742 const OriginProps& aOriginProps) {
7743 AssertIsOnIOThread();
7745 // This handles changes in origin string generation from nsIPrincipal,
7746 // especially the stripping of obsolete origin attributes like addonId.
7747 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
7748 if (renamed) {
7749 return NS_OK;
7752 if (aOriginProps.mNeedsRestore) {
7753 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7754 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7755 aOriginProps.mOriginMetadata)));
7758 if (aOriginProps.mNeedsRestore2) {
7759 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7760 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7761 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7764 return NS_OK;
7767 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
7768 OriginProps& aOriginProps, bool* aRemoved) {
7769 AssertIsOnIOThread();
7770 MOZ_ASSERT(aRemoved);
7772 QM_TRY(MOZ_TO_RESULT(
7773 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1)));
7775 int64_t timestamp;
7776 nsCString group;
7777 nsCString origin;
7778 Nullable<bool> isApp;
7779 QM_WARNONLY_TRY_UNWRAP(
7780 const auto maybeDirectoryMetadata,
7781 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7782 timestamp, group, origin, isApp)));
7783 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7784 aOriginProps.mNeedsRestore = true;
7787 nsCString suffix;
7788 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
7789 MOZ_TO_RESULT(GetDirectoryMetadata2(
7790 aOriginProps.mDirectory.get(), timestamp, suffix,
7791 group, origin, isApp.SetValue())));
7792 if (!maybeDirectoryMetadata2) {
7793 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7794 aOriginProps.mNeedsRestore2 = true;
7795 } else {
7796 aOriginProps.mTimestamp = timestamp;
7799 *aRemoved = false;
7800 return NS_OK;
7803 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
7804 const OriginProps& aOriginProps) {
7805 AssertIsOnIOThread();
7807 if (aOriginProps.mNeedsRestore) {
7808 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7809 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7810 aOriginProps.mOriginMetadata)));
7813 if (aOriginProps.mNeedsRestore2) {
7814 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7815 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7816 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7819 return NS_OK;
7822 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
7823 OriginProps& aOriginProps, bool* aRemoved) {
7824 AssertIsOnIOThread();
7825 MOZ_ASSERT(aRemoved);
7827 QM_TRY(MOZ_TO_RESULT(
7828 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2)));
7830 int64_t timestamp;
7831 nsCString group;
7832 nsCString origin;
7833 Nullable<bool> isApp;
7834 QM_WARNONLY_TRY_UNWRAP(
7835 const auto maybeDirectoryMetadata,
7836 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7837 timestamp, group, origin, isApp)));
7838 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7839 aOriginProps.mNeedsRestore = true;
7842 nsCString suffix;
7843 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
7844 MOZ_TO_RESULT(GetDirectoryMetadata2(
7845 aOriginProps.mDirectory.get(), timestamp, suffix,
7846 group, origin, isApp.SetValue())));
7847 if (!maybeDirectoryMetadata2) {
7848 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7849 aOriginProps.mNeedsRestore2 = true;
7850 } else {
7851 aOriginProps.mTimestamp = timestamp;
7854 *aRemoved = false;
7855 return NS_OK;
7858 nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
7859 const OriginProps& aOriginProps) {
7860 AssertIsOnIOThread();
7862 if (aOriginProps.mNeedsRestore) {
7863 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7864 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7865 aOriginProps.mOriginMetadata)));
7868 if (aOriginProps.mNeedsRestore2) {
7869 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7870 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7871 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7874 return NS_OK;
7877 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
7878 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
7879 AssertIsOnIOThread();
7881 if (Client::IsDeprecatedClient(aLeafName)) {
7882 QM_WARNING("Deleting deprecated %s client!",
7883 NS_ConvertUTF16toUTF8(aLeafName).get());
7885 QM_TRY(MOZ_TO_RESULT(aFile->Remove(true)));
7887 aRemoved = true;
7888 } else {
7889 aRemoved = false;
7892 return NS_OK;
7895 nsresult RestoreDirectoryMetadata2Helper::Init() {
7896 AssertIsOnIOThread();
7897 MOZ_ASSERT(mDirectory);
7899 nsCOMPtr<nsIFile> parentDir;
7900 QM_TRY(MOZ_TO_RESULT(mDirectory->GetParent(getter_AddRefs(parentDir))));
7902 const auto maybePersistenceType =
7903 PersistenceTypeFromFile(*parentDir, fallible);
7904 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
7906 mPersistenceType.init(maybePersistenceType.value());
7908 return NS_OK;
7911 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
7912 OriginProps originProps(WrapMovingNotNull(mDirectory));
7913 QM_TRY(MOZ_TO_RESULT(originProps.Init(
7914 [&self = *this](const auto& aSpec) { return *self.mPersistenceType; })));
7916 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE);
7918 originProps.mTimestamp = GetOriginLastModifiedTime(originProps);
7920 mOriginProps.AppendElement(std::move(originProps));
7922 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
7924 return NS_OK;
7927 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
7928 const OriginProps& aOriginProps) {
7929 AssertIsOnIOThread();
7931 // We don't have any approach to restore aPersisted, so reset it to false.
7932 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7933 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7934 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7936 return NS_OK;
7939 } // namespace mozilla::dom::quota