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"
11 #include "InitializationTypes.h"
12 #include "OriginScope.h"
13 #include "QuotaCommon.h"
14 #include "QuotaManager.h"
15 #include "QuotaObject.h"
16 #include "UsageInfo.h"
28 #include <type_traits>
30 #include "DirectoryLockImpl.h"
31 #include "ErrorList.h"
32 #include "MainThreadUtils.h"
33 #include "mozIStorageAsyncConnection.h"
34 #include "mozIStorageConnection.h"
35 #include "mozIStorageService.h"
36 #include "mozIStorageStatement.h"
37 #include "mozStorageCID.h"
38 #include "mozStorageHelper.h"
39 #include "mozilla/AlreadyAddRefed.h"
40 #include "mozilla/Assertions.h"
41 #include "mozilla/Atomics.h"
42 #include "mozilla/Attributes.h"
43 #include "mozilla/AutoRestore.h"
44 #include "mozilla/BasePrincipal.h"
45 #include "mozilla/CheckedInt.h"
46 #include "mozilla/CondVar.h"
47 #include "mozilla/InitializedOnce.h"
48 #include "mozilla/Logging.h"
49 #include "mozilla/MacroForEach.h"
50 #include "mozilla/Maybe.h"
51 #include "mozilla/Mutex.h"
52 #include "mozilla/NotNull.h"
53 #include "mozilla/OriginAttributes.h"
54 #include "mozilla/Preferences.h"
55 #include "mozilla/ProfilerLabels.h"
56 #include "mozilla/RefPtr.h"
57 #include "mozilla/Result.h"
58 #include "mozilla/ResultExtensions.h"
59 #include "mozilla/ScopeExit.h"
60 #include "mozilla/Services.h"
61 #include "mozilla/SpinEventLoopUntil.h"
62 #include "mozilla/StaticPrefs_dom.h"
63 #include "mozilla/StaticPtr.h"
64 #include "mozilla/Telemetry.h"
65 #include "mozilla/TelemetryHistogramEnums.h"
66 #include "mozilla/TextUtils.h"
67 #include "mozilla/TimeStamp.h"
68 #include "mozilla/UniquePtr.h"
69 #include "mozilla/Unused.h"
70 #include "mozilla/Variant.h"
71 #include "mozilla/dom/FlippedOnce.h"
72 #include "mozilla/dom/LocalStorageCommon.h"
73 #include "mozilla/dom/StorageActivityService.h"
74 #include "mozilla/dom/StorageDBUpdater.h"
75 #include "mozilla/dom/StorageTypeBinding.h"
76 #include "mozilla/dom/cache/QuotaClient.h"
77 #include "mozilla/dom/indexedDB/ActorsParent.h"
78 #include "mozilla/dom/ipc/IdType.h"
79 #include "mozilla/dom/localstorage/ActorsParent.h"
80 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
81 #include "mozilla/dom/quota/Client.h"
82 #include "mozilla/dom/quota/DirectoryLock.h"
83 #include "mozilla/dom/quota/PersistenceType.h"
84 #include "mozilla/dom/quota/PQuota.h"
85 #include "mozilla/dom/quota/PQuotaParent.h"
86 #include "mozilla/dom/quota/PQuotaRequest.h"
87 #include "mozilla/dom/quota/PQuotaRequestParent.h"
88 #include "mozilla/dom/quota/PQuotaUsageRequest.h"
89 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
90 #include "mozilla/dom/simpledb/ActorsParent.h"
91 #include "mozilla/fallible.h"
92 #include "mozilla/ipc/BackgroundChild.h"
93 #include "mozilla/ipc/BackgroundParent.h"
94 #include "mozilla/ipc/PBackgroundChild.h"
95 #include "mozilla/ipc/PBackgroundSharedTypes.h"
96 #include "mozilla/ipc/ProtocolUtils.h"
97 #include "mozilla/net/MozURL.h"
98 #include "nsAppDirectoryServiceDefs.h"
99 #include "nsBaseHashtable.h"
100 #include "nsCOMPtr.h"
101 #include "nsCRTGlue.h"
102 #include "nsCharSeparatedTokenizer.h"
103 #include "nsClassHashtable.h"
104 #include "nsComponentManagerUtils.h"
105 #include "nsContentUtils.h"
106 #include "nsTHashMap.h"
108 #include "nsDirectoryServiceUtils.h"
110 #include "nsHashKeys.h"
111 #include "nsIBinaryInputStream.h"
112 #include "nsIBinaryOutputStream.h"
113 #include "nsIConsoleService.h"
114 #include "nsIDirectoryEnumerator.h"
115 #include "nsIEventTarget.h"
117 #include "nsIFileStreams.h"
118 #include "nsIInputStream.h"
119 #include "nsIObjectInputStream.h"
120 #include "nsIObjectOutputStream.h"
121 #include "nsIObserver.h"
122 #include "nsIObserverService.h"
123 #include "nsIOutputStream.h"
124 #include "nsIPlatformInfo.h"
125 #include "nsIPrincipal.h"
126 #include "nsIRunnable.h"
127 #include "nsIScriptObjectPrincipal.h"
128 #include "nsISupports.h"
129 #include "nsISupportsPrimitives.h"
130 #include "nsIThread.h"
131 #include "nsITimer.h"
133 #include "nsIWidget.h"
134 #include "nsLiteralString.h"
135 #include "nsNetUtil.h"
136 #include "nsPIDOMWindow.h"
137 #include "nsPrintfCString.h"
138 #include "nsServiceManagerUtils.h"
139 #include "nsString.h"
140 #include "nsStringFlags.h"
141 #include "nsStringFwd.h"
142 #include "nsTArray.h"
143 #include "nsTHashtable.h"
144 #include "nsTLiteralString.h"
145 #include "nsTPromiseFlatString.h"
146 #include "nsTStringRepr.h"
147 #include "nsThreadUtils.h"
148 #include "nsURLHelper.h"
150 #include "nsXPCOMCID.h"
151 #include "nsXULAppAPI.h"
152 #include "prinrval.h"
154 #include "prthread.h"
157 #define DISABLE_ASSERTS_FOR_FUZZING 0
159 #if DISABLE_ASSERTS_FOR_FUZZING
160 # define ASSERT_UNLESS_FUZZING(...) \
164 # define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
167 // As part of bug 1536596 in order to identify the remaining sources of
168 // principal info inconsistencies, we have added anonymized crash logging and
169 // are temporarily making these checks occur on both debug and optimized
170 // nightly, dev-edition, and early beta builds through use of
171 // EARLY_BETA_OR_EARLIER during Firefox 82. The plan is to return this
172 // condition to MOZ_DIAGNOSTIC_ASSERT_ENABLED during Firefox 84 at the latest.
173 // The analysis and disabling is tracked by bug 1536596.
175 #ifdef EARLY_BETA_OR_EARLIER
176 # define QM_PRINCIPALINFO_VERIFICATION_ENABLED
179 // The amount of time, in milliseconds, that our IO thread will stay alive
180 // after the last event it processes.
181 #define DEFAULT_THREAD_TIMEOUT_MS 30000
184 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
187 #define SHUTDOWN_FORCE_KILL_TIMEOUT_MS 5000
190 * Automatically crash the browser if shutdown of a quota client takes this
191 * long. We've chosen a value that is long enough that it is unlikely for the
192 * problem to be falsely triggered by slow system I/O. We've also chosen a
193 * value long enough so that automated tests should time out and fail if
194 * shutdown of a quota client takes too long. Also, this value is long enough
195 * so that testers can notice the timeout; we want to know about the timeouts,
196 * not hide them. On the other hand this value is less than 60 seconds which is
197 * used by nsTerminator to crash a hung main process.
199 #define SHUTDOWN_FORCE_CRASH_TIMEOUT_MS 45000
201 // profile-before-change, when we need to shut down quota manager
202 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
205 #define MB *1024ULL KB
206 #define GB *1024ULL MB
208 namespace mozilla::dom::quota
{
210 using namespace mozilla::ipc
;
211 using mozilla::net::MozURL
;
213 // We want profiles to be platform-independent so we always need to replace
214 // the same characters on every platform. Windows has the most extensive set
215 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
216 // FILE_PATH_SEPARATOR.
217 const char QuotaManager::kReplaceChars
[] = CONTROL_CHARACTERS
"/:*?\"<>|\\";
221 template <typename T
>
222 void AssertNoOverflow(uint64_t aDest
, T aArg
);
224 /*******************************************************************************
226 ******************************************************************************/
228 const uint32_t kSQLitePageSizeOverride
= 512;
230 // Important version history:
231 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
232 // which caused Firefox 57 release concerns because the major schema upgrade
233 // means anyone downgrading to Firefox 56 will experience a non-operational
234 // QuotaManager and all of its clients.
235 // - Bug 1404344 got very concerned about that and so we decided to effectively
236 // rename 3.0 to 2.1, effective in Firefox 57. This works because post
237 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
238 // increases. It also works because all the upgrade did was give the DOM
239 // Cache API QuotaClient an opportunity to create its newly added .padding
240 // files during initialization/upgrade, which isn't functionally necessary as
241 // that can be done on demand.
243 // Major storage version. Bump for backwards-incompatible changes.
244 // (The next major version should be 4 to distinguish from the Bug 1290481
246 const uint32_t kMajorStorageVersion
= 2;
248 // Minor storage version. Bump for backwards-compatible changes.
249 const uint32_t kMinorStorageVersion
= 3;
251 // The storage version we store in the SQLite database is a (signed) 32-bit
252 // integer. The major version is left-shifted 16 bits so the max value is
253 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
254 static_assert(kMajorStorageVersion
<= 0xFFFF,
255 "Major version needs to fit in 16 bits.");
256 static_assert(kMinorStorageVersion
<= 0xFFFF,
257 "Minor version needs to fit in 16 bits.");
259 const int32_t kStorageVersion
=
260 int32_t((kMajorStorageVersion
<< 16) + kMinorStorageVersion
);
262 // See comments above about why these are a thing.
263 const int32_t kHackyPreDowngradeStorageVersion
= int32_t((3 << 16) + 0);
264 const int32_t kHackyPostDowngradeStorageVersion
= int32_t((2 << 16) + 1);
266 static_assert(static_cast<uint32_t>(StorageType::Persistent
) ==
267 static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT
),
268 "Enum values should match.");
270 static_assert(static_cast<uint32_t>(StorageType::Temporary
) ==
271 static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY
),
272 "Enum values should match.");
274 static_assert(static_cast<uint32_t>(StorageType::Default
) ==
275 static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT
),
276 "Enum values should match.");
278 const char kChromeOrigin
[] = "chrome";
279 const char kAboutHomeOriginPrefix
[] = "moz-safe-about:home";
280 const char kIndexedDBOriginPrefix
[] = "indexeddb://";
281 const char kResourceOriginPrefix
[] = "resource://";
283 constexpr auto kPersistentOriginTelemetryKey
= "PersistentOrigin"_ns
;
284 constexpr auto kTemporaryOriginTelemetryKey
= "TemporaryOrigin"_ns
;
286 constexpr auto kStorageName
= u
"storage"_ns
;
287 constexpr auto kSQLiteSuffix
= u
".sqlite"_ns
;
289 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
290 #define PERSISTENT_DIRECTORY_NAME u"persistent"
291 #define PERMANENT_DIRECTORY_NAME u"permanent"
292 #define TEMPORARY_DIRECTORY_NAME u"temporary"
293 #define DEFAULT_DIRECTORY_NAME u"default"
295 // The name of the file that we use to load/save the last access time of an
297 // XXX We should get rid of old metadata files at some point, bug 1343576.
298 #define METADATA_FILE_NAME u".metadata"
299 #define METADATA_TMP_FILE_NAME u".metadata-tmp"
300 #define METADATA_V2_FILE_NAME u".metadata-v2"
301 #define METADATA_V2_TMP_FILE_NAME u".metadata-v2-tmp"
303 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
304 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
305 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
307 const int32_t kLocalStorageArchiveVersion
= 4;
309 const char kProfileDoChangeTopic
[] = "profile-do-change";
311 const int32_t kCacheVersion
= 2;
313 /******************************************************************************
315 ******************************************************************************/
317 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion
,
318 uint32_t aMinorStorageVersion
) {
319 return int32_t((aMajorStorageVersion
<< 16) + aMinorStorageVersion
);
322 uint32_t GetMajorStorageVersion(int32_t aStorageVersion
) {
323 return uint32_t(aStorageVersion
>> 16);
326 nsresult
CreateTables(mozIStorageConnection
* aConnection
) {
327 AssertIsOnIOThread();
328 MOZ_ASSERT(aConnection
);
332 aConnection
->ExecuteSimpleSQL("CREATE TABLE database"
333 "( cache_version INTEGER NOT NULL DEFAULT 0"
338 QM_TRY_INSPECT(const int32_t& storageVersion
,
339 MOZ_TO_RESULT_INVOKE(aConnection
, GetSchemaVersion
));
341 MOZ_ASSERT(storageVersion
== 0);
345 QM_TRY(aConnection
->SetSchemaVersion(kStorageVersion
));
350 Result
<int32_t, nsresult
> LoadCacheVersion(mozIStorageConnection
& aConnection
) {
351 AssertIsOnIOThread();
353 QM_TRY_INSPECT(const auto& stmt
,
354 CreateAndExecuteSingleStepStatement
<
355 SingleStepResult::ReturnNullIfNoResult
>(
356 aConnection
, "SELECT cache_version FROM database"_ns
));
358 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FILE_CORRUPTED
));
360 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(stmt
, GetInt32
, 0));
363 nsresult
SaveCacheVersion(mozIStorageConnection
& aConnection
,
365 AssertIsOnIOThread();
369 MOZ_TO_RESULT_INVOKE_TYPED(
370 nsCOMPtr
<mozIStorageStatement
>, aConnection
, CreateStatement
,
371 "UPDATE database SET cache_version = :version;"_ns
));
373 QM_TRY(stmt
->BindInt32ByName("version"_ns
, aVersion
));
375 QM_TRY(stmt
->Execute());
380 nsresult
CreateCacheTables(mozIStorageConnection
& aConnection
) {
381 AssertIsOnIOThread();
385 aConnection
.ExecuteSimpleSQL("CREATE TABLE cache"
386 "( valid INTEGER NOT NULL DEFAULT 0"
387 ", build_id TEXT NOT NULL DEFAULT ''"
390 // Table `repository`
392 aConnection
.ExecuteSimpleSQL("CREATE TABLE repository"
393 "( id INTEGER PRIMARY KEY"
394 ", name TEXT NOT NULL"
399 aConnection
.ExecuteSimpleSQL("CREATE TABLE origin"
400 "( repository_id INTEGER NOT NULL"
402 ", group_ TEXT NOT NULL"
403 ", origin TEXT NOT NULL"
404 ", client_usages TEXT NOT NULL"
405 ", usage INTEGER NOT NULL"
406 ", last_access_time INTEGER NOT NULL"
407 ", accessed INTEGER NOT NULL"
408 ", persisted INTEGER NOT NULL"
409 ", PRIMARY KEY (repository_id, origin)"
410 ", FOREIGN KEY (repository_id) "
411 "REFERENCES repository(id) "
416 QM_TRY_INSPECT(const int32_t& cacheVersion
, LoadCacheVersion(aConnection
));
417 MOZ_ASSERT(cacheVersion
== 0);
421 QM_TRY(SaveCacheVersion(aConnection
, kCacheVersion
));
426 nsresult
InvalidateCache(mozIStorageConnection
& aConnection
) {
427 AssertIsOnIOThread();
429 static constexpr auto kDeleteCacheQuery
= "DELETE FROM origin;"_ns
;
430 static constexpr auto kSetInvalidFlagQuery
= "UPDATE cache SET valid = 0"_ns
;
432 QM_TRY(QM_OR_ELSE_WARN(
433 ([&]() -> Result
<Ok
, nsresult
> {
434 mozStorageTransaction
transaction(&aConnection
, false);
436 QM_TRY(transaction
.Start());
437 QM_TRY(aConnection
.ExecuteSimpleSQL(kDeleteCacheQuery
));
438 QM_TRY(aConnection
.ExecuteSimpleSQL(kSetInvalidFlagQuery
));
439 QM_TRY(transaction
.Commit());
443 ([&](const nsresult rv
) -> Result
<Ok
, nsresult
> {
444 QM_TRY(aConnection
.ExecuteSimpleSQL(kSetInvalidFlagQuery
));
451 nsresult
UpgradeCacheFrom1To2(mozIStorageConnection
& aConnection
) {
452 AssertIsOnIOThread();
454 QM_TRY(aConnection
.ExecuteSimpleSQL(
455 "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns
));
457 QM_TRY(InvalidateCache(aConnection
));
461 QM_TRY_INSPECT(const int32_t& cacheVersion
, LoadCacheVersion(aConnection
));
463 MOZ_ASSERT(cacheVersion
== 1);
467 QM_TRY(SaveCacheVersion(aConnection
, 2));
472 Result
<bool, nsresult
> MaybeCreateOrUpgradeCache(
473 mozIStorageConnection
& aConnection
) {
474 bool cacheUsable
= true;
476 QM_TRY_UNWRAP(int32_t cacheVersion
, LoadCacheVersion(aConnection
));
478 if (cacheVersion
> kCacheVersion
) {
480 } else if (cacheVersion
!= kCacheVersion
) {
481 const bool newCache
= !cacheVersion
;
483 mozStorageTransaction
transaction(
484 &aConnection
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
486 QM_TRY(transaction
.Start());
489 QM_TRY(CreateCacheTables(aConnection
));
493 QM_TRY_INSPECT(const int32_t& cacheVersion
,
494 LoadCacheVersion(aConnection
));
495 MOZ_ASSERT(cacheVersion
== kCacheVersion
);
499 QM_TRY(aConnection
.ExecuteSimpleSQL(
500 nsLiteralCString("INSERT INTO cache (valid, build_id) "
503 nsCOMPtr
<mozIStorageStatement
> insertStmt
;
505 for (const PersistenceType persistenceType
: kAllPersistenceTypes
) {
507 MOZ_ALWAYS_SUCCEEDS(insertStmt
->Reset());
509 QM_TRY_UNWRAP(insertStmt
, MOZ_TO_RESULT_INVOKE_TYPED(
510 nsCOMPtr
<mozIStorageStatement
>,
511 aConnection
, CreateStatement
,
512 "INSERT INTO repository (id, name) "
513 "VALUES (:id, :name)"_ns
));
516 QM_TRY(insertStmt
->BindInt32ByName("id"_ns
, persistenceType
));
518 QM_TRY(insertStmt
->BindUTF8StringByName(
519 "name"_ns
, PersistenceTypeToString(persistenceType
)));
521 QM_TRY(insertStmt
->Execute());
524 // This logic needs to change next time we change the cache!
525 static_assert(kCacheVersion
== 2,
526 "Upgrade function needed due to cache version increase.");
528 while (cacheVersion
!= kCacheVersion
) {
529 if (cacheVersion
== 1) {
530 QM_TRY(UpgradeCacheFrom1To2(aConnection
));
532 QM_FAIL(Err(NS_ERROR_FAILURE
), []() {
534 "Unable to initialize cache, no upgrade path is "
539 QM_TRY_UNWRAP(cacheVersion
, LoadCacheVersion(aConnection
));
542 MOZ_ASSERT(cacheVersion
== kCacheVersion
);
545 QM_TRY(transaction
.Commit());
551 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
> CreateWebAppsStoreConnection(
552 nsIFile
& aWebAppsStoreFile
, mozIStorageService
& aStorageService
) {
553 AssertIsOnIOThread();
555 // Check if the old database exists at all.
556 QM_TRY_INSPECT(const bool& exists
,
557 MOZ_TO_RESULT_INVOKE(aWebAppsStoreFile
, Exists
));
560 // webappsstore.sqlite doesn't exist, return a null connection.
561 return nsCOMPtr
<mozIStorageConnection
>{};
564 QM_TRY_INSPECT(const bool& isDirectory
,
565 MOZ_TO_RESULT_INVOKE(aWebAppsStoreFile
, IsDirectory
));
568 QM_WARNING("webappsstore.sqlite is not a file!");
569 return nsCOMPtr
<mozIStorageConnection
>{};
572 QM_TRY_INSPECT(const auto& connection
,
574 MOZ_TO_RESULT_INVOKE_TYPED(
575 nsCOMPtr
<mozIStorageConnection
>, aStorageService
,
576 OpenUnsharedDatabase
, &aWebAppsStoreFile
),
577 ([](const nsresult rv
)
578 -> Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
> {
579 if (IsDatabaseCorruptionError(rv
)) {
580 // Don't throw an error, leave a corrupted webappsstore
581 // database as it is.
582 return nsCOMPtr
<mozIStorageConnection
>{};
589 // Don't propagate an error, leave a non-updateable webappsstore database as
591 QM_TRY(StorageDBUpdater::Update(connection
),
592 nsCOMPtr
<mozIStorageConnection
>{});
598 Result
<nsCOMPtr
<nsIFile
>, nsresult
> GetLocalStorageArchiveFile(
599 const nsAString
& aDirectoryPath
) {
600 AssertIsOnIOThread();
601 MOZ_ASSERT(!aDirectoryPath
.IsEmpty());
603 QM_TRY_UNWRAP(auto lsArchiveFile
, QM_NewLocalFile(aDirectoryPath
));
605 QM_TRY(lsArchiveFile
->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME
)));
607 return lsArchiveFile
;
610 Result
<nsCOMPtr
<nsIFile
>, nsresult
> GetLocalStorageArchiveTmpFile(
611 const nsAString
& aDirectoryPath
) {
612 AssertIsOnIOThread();
613 MOZ_ASSERT(!aDirectoryPath
.IsEmpty());
615 QM_TRY_UNWRAP(auto lsArchiveTmpFile
, QM_NewLocalFile(aDirectoryPath
));
617 QM_TRY(lsArchiveTmpFile
->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME
)));
619 return lsArchiveTmpFile
;
622 Result
<bool, nsresult
> IsLocalStorageArchiveInitialized(
623 mozIStorageConnection
& aConnection
) {
624 AssertIsOnIOThread();
626 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(aConnection
, TableExists
, "database"_ns
));
629 nsresult
InitializeLocalStorageArchive(mozIStorageConnection
* aConnection
) {
630 AssertIsOnIOThread();
631 MOZ_ASSERT(aConnection
);
635 QM_TRY_INSPECT(const auto& initialized
,
636 IsLocalStorageArchiveInitialized(*aConnection
));
637 MOZ_ASSERT(!initialized
);
641 QM_TRY(aConnection
->ExecuteSimpleSQL(
642 "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns
));
646 MOZ_TO_RESULT_INVOKE_TYPED(
647 nsCOMPtr
<mozIStorageStatement
>, aConnection
, CreateStatement
,
648 "INSERT INTO database (version) VALUES (:version)"_ns
));
650 QM_TRY(stmt
->BindInt32ByName("version"_ns
, 0));
651 QM_TRY(stmt
->Execute());
656 Result
<int32_t, nsresult
> LoadLocalStorageArchiveVersion(
657 mozIStorageConnection
& aConnection
) {
658 AssertIsOnIOThread();
660 QM_TRY_INSPECT(const auto& stmt
,
661 CreateAndExecuteSingleStepStatement
<
662 SingleStepResult::ReturnNullIfNoResult
>(
663 aConnection
, "SELECT version FROM database"_ns
));
665 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FILE_CORRUPTED
));
667 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(stmt
, GetInt32
, 0));
670 nsresult
SaveLocalStorageArchiveVersion(mozIStorageConnection
* aConnection
,
672 AssertIsOnIOThread();
673 MOZ_ASSERT(aConnection
);
675 nsCOMPtr
<mozIStorageStatement
> stmt
;
676 nsresult rv
= aConnection
->CreateStatement(
677 "UPDATE database SET version = :version;"_ns
, getter_AddRefs(stmt
));
678 if (NS_WARN_IF(NS_FAILED(rv
))) {
682 rv
= stmt
->BindInt32ByName("version"_ns
, aVersion
);
683 if (NS_WARN_IF(NS_FAILED(rv
))) {
687 rv
= stmt
->Execute();
688 if (NS_WARN_IF(NS_FAILED(rv
))) {
695 template <typename FileFunc
, typename DirectoryFunc
>
696 Result
<mozilla::Ok
, nsresult
> CollectEachFileEntry(
697 nsIFile
& aDirectory
, const FileFunc
& aFileFunc
,
698 const DirectoryFunc
& aDirectoryFunc
) {
699 AssertIsOnIOThread();
701 return CollectEachFile(
703 [&aFileFunc
, &aDirectoryFunc
](
704 const nsCOMPtr
<nsIFile
>& file
) -> Result
<mozilla::Ok
, nsresult
> {
705 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*file
));
707 switch (dirEntryKind
) {
708 case nsIFileKind::ExistsAsDirectory
:
709 return aDirectoryFunc(file
);
711 case nsIFileKind::ExistsAsFile
:
712 return aFileFunc(file
);
714 case nsIFileKind::DoesNotExist
:
715 // Ignore files that got removed externally while iterating.
723 /******************************************************************************
724 * Quota manager class declarations
725 ******************************************************************************/
729 class QuotaManager::Observer final
: public nsIObserver
{
730 static Observer
* sInstance
;
732 bool mPendingProfileChange
;
733 bool mShutdownComplete
;
736 static nsresult
Initialize();
738 static void ShutdownCompleted();
741 Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
742 MOZ_ASSERT(NS_IsMainThread());
745 ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
757 /*******************************************************************************
758 * Local class declarations
759 ******************************************************************************/
763 // XXX Change this not to derive from AutoTArray.
764 class ClientUsageArray final
765 : public AutoTArray
<Maybe
<uint64_t>, Client::TYPE_MAX
> {
767 ClientUsageArray() { SetLength(Client::TypeMax()); }
769 void Serialize(nsACString
& aText
) const;
771 nsresult
Deserialize(const nsACString
& aText
);
773 ClientUsageArray
Clone() const {
774 ClientUsageArray res
;
780 class OriginInfo final
{
781 friend class GroupInfo
;
782 friend class QuotaManager
;
783 friend class QuotaObject
;
786 OriginInfo(GroupInfo
* aGroupInfo
, const nsACString
& aOrigin
,
787 const ClientUsageArray
& aClientUsages
, uint64_t aUsage
,
788 int64_t aAccessTime
, bool aPersisted
, bool aDirectoryExists
);
790 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo
)
792 GroupInfo
* GetGroupInfo() const { return mGroupInfo
; }
794 const nsCString
& Origin() const { return mOrigin
; }
796 int64_t LockedUsage() const {
797 AssertCurrentThreadOwnsQuotaMutex();
800 QuotaManager
* quotaManager
= QuotaManager::Get();
801 MOZ_ASSERT(quotaManager
);
804 for (Client::Type type
: quotaManager
->AllClientTypes()) {
805 AssertNoOverflow(usage
, mClientUsages
[type
].valueOr(0));
806 usage
+= mClientUsages
[type
].valueOr(0);
808 MOZ_ASSERT(mUsage
== usage
);
814 int64_t LockedAccessTime() const {
815 AssertCurrentThreadOwnsQuotaMutex();
820 bool LockedPersisted() const {
821 AssertCurrentThreadOwnsQuotaMutex();
826 OriginMetadata
FlattenToOriginMetadata() const;
828 nsresult
LockedBindToStatement(mozIStorageStatement
* aStatement
) const;
831 // Private destructor, to discourage deletion outside of Release():
833 MOZ_COUNT_DTOR(OriginInfo
);
835 MOZ_ASSERT(!mQuotaObjects
.Count());
838 void LockedDecreaseUsage(Client::Type aClientType
, int64_t aSize
);
840 void LockedResetUsageForClient(Client::Type aClientType
);
842 UsageInfo
LockedGetUsageForClient(Client::Type aClientType
);
844 void LockedUpdateAccessTime(int64_t aAccessTime
) {
845 AssertCurrentThreadOwnsQuotaMutex();
847 mAccessTime
= aAccessTime
;
853 void LockedPersist();
855 nsTHashMap
<nsStringHashKey
, NotNull
<QuotaObject
*>> mQuotaObjects
;
856 ClientUsageArray mClientUsages
;
857 GroupInfo
* mGroupInfo
;
858 const nsCString mOrigin
;
864 * In some special cases like the LocalStorage client where it's possible to
865 * create a Quota-using representation but not actually write any data, we
866 * want to be able to track quota for an origin without creating its origin
867 * directory or the per-client files until they are actually needed to store
868 * data. In those cases, the OriginInfo will be created by
869 * EnsureQuotaForOrigin and the resulting mDirectoryExists will be false until
870 * the origin actually needs to be created. It is possible for mUsage to be
871 * greater than zero while mDirectoryExists is false, representing a state
872 * where a client like LocalStorage has reserved quota for disk writes, but
873 * has not yet flushed the data to disk.
875 bool mDirectoryExists
;
878 class OriginInfoAccessTimeComparator
{
880 bool Equals(const NotNull
<RefPtr
<const OriginInfo
>>& a
,
881 const NotNull
<RefPtr
<const OriginInfo
>>& b
) const {
882 return a
->LockedAccessTime() == b
->LockedAccessTime();
885 bool LessThan(const NotNull
<RefPtr
<const OriginInfo
>>& a
,
886 const NotNull
<RefPtr
<const OriginInfo
>>& b
) const {
887 return a
->LockedAccessTime() < b
->LockedAccessTime();
891 class GroupInfo final
{
892 friend class GroupInfoPair
;
893 friend class OriginInfo
;
894 friend class QuotaManager
;
895 friend class QuotaObject
;
898 GroupInfo(GroupInfoPair
* aGroupInfoPair
, PersistenceType aPersistenceType
)
899 : mGroupInfoPair(aGroupInfoPair
),
900 mPersistenceType(aPersistenceType
),
902 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
904 MOZ_COUNT_CTOR(GroupInfo
);
907 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo
)
909 PersistenceType
GetPersistenceType() const { return mPersistenceType
; }
912 // Private destructor, to discourage deletion outside of Release():
913 MOZ_COUNTED_DTOR(GroupInfo
)
915 already_AddRefed
<OriginInfo
> LockedGetOriginInfo(const nsACString
& aOrigin
);
917 void LockedAddOriginInfo(NotNull
<RefPtr
<OriginInfo
>>&& aOriginInfo
);
919 void LockedAdjustUsageForRemovedOriginInfo(const OriginInfo
& aOriginInfo
);
921 void LockedRemoveOriginInfo(const nsACString
& aOrigin
);
923 void LockedRemoveOriginInfos();
925 bool LockedHasOriginInfos() {
926 AssertCurrentThreadOwnsQuotaMutex();
928 return !mOriginInfos
.IsEmpty();
931 nsTArray
<NotNull
<RefPtr
<OriginInfo
>>> mOriginInfos
;
933 GroupInfoPair
* mGroupInfoPair
;
934 PersistenceType mPersistenceType
;
938 // XXX Consider a new name for this class, it has other data members now
939 // (besides two GroupInfo objects).
940 class GroupInfoPair
{
942 GroupInfoPair(const nsACString
& aSuffix
, const nsACString
& aGroup
)
943 : mSuffix(aSuffix
), mGroup(aGroup
) {
944 MOZ_COUNT_CTOR(GroupInfoPair
);
947 MOZ_COUNTED_DTOR(GroupInfoPair
)
949 const nsCString
& Suffix() const { return mSuffix
; }
951 const nsCString
& Group() const { return mGroup
; }
953 RefPtr
<GroupInfo
> LockedGetGroupInfo(PersistenceType aPersistenceType
) {
954 AssertCurrentThreadOwnsQuotaMutex();
955 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
957 return GetGroupInfoForPersistenceType(aPersistenceType
);
960 void LockedSetGroupInfo(PersistenceType aPersistenceType
,
961 GroupInfo
* aGroupInfo
) {
962 AssertCurrentThreadOwnsQuotaMutex();
963 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
965 RefPtr
<GroupInfo
>& groupInfo
=
966 GetGroupInfoForPersistenceType(aPersistenceType
);
967 groupInfo
= aGroupInfo
;
970 void LockedClearGroupInfo(PersistenceType aPersistenceType
) {
971 AssertCurrentThreadOwnsQuotaMutex();
972 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
974 RefPtr
<GroupInfo
>& groupInfo
=
975 GetGroupInfoForPersistenceType(aPersistenceType
);
979 bool LockedHasGroupInfos() {
980 AssertCurrentThreadOwnsQuotaMutex();
982 return mTemporaryStorageGroupInfo
|| mDefaultStorageGroupInfo
;
986 RefPtr
<GroupInfo
>& GetGroupInfoForPersistenceType(
987 PersistenceType aPersistenceType
);
989 const nsCString mSuffix
;
990 const nsCString mGroup
;
991 RefPtr
<GroupInfo
> mTemporaryStorageGroupInfo
;
992 RefPtr
<GroupInfo
> mDefaultStorageGroupInfo
;
997 class CollectOriginsHelper final
: public Runnable
{
998 uint64_t mMinSizeToBeFreed
;
1003 // The members below are protected by mMutex.
1004 nsTArray
<RefPtr
<OriginDirectoryLock
>> mLocks
;
1005 uint64_t mSizeToBeFreed
;
1009 CollectOriginsHelper(mozilla::Mutex
& aMutex
, uint64_t aMinSizeToBeFreed
);
1011 // Blocks the current thread until origins are collected on the main thread.
1012 // The returned value contains an aggregate size of those origins.
1013 int64_t BlockAndReturnOriginsForEviction(
1014 nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
);
1017 ~CollectOriginsHelper() = default;
1023 class OriginOperationBase
: public BackgroundThreadObject
, public Runnable
{
1025 nsresult mResultCode
;
1031 // Running quota manager initialization on the owning thread.
1032 State_CreatingQuotaManager
,
1034 // Running on the owning thread in the listener for OpenDirectory.
1035 State_DirectoryOpenPending
,
1037 // Running on the IO thread.
1038 State_DirectoryWorkOpen
,
1040 // Running on the owning thread after all work is done.
1041 State_UnblockingOpen
,
1049 bool mActorDestroyed
;
1052 bool mNeedsQuotaManagerInit
;
1053 bool mNeedsStorageInit
;
1056 void NoteActorDestroyed() {
1057 AssertIsOnOwningThread();
1059 mActorDestroyed
= true;
1062 bool IsActorDestroyed() const {
1063 AssertIsOnOwningThread();
1065 return mActorDestroyed
;
1069 explicit OriginOperationBase(
1070 nsIEventTarget
* aOwningThread
= GetCurrentEventTarget())
1071 : BackgroundThreadObject(aOwningThread
),
1072 Runnable("dom::quota::OriginOperationBase"),
1074 mState(State_Initial
),
1075 mActorDestroyed(false),
1076 mNeedsQuotaManagerInit(false),
1077 mNeedsStorageInit(false) {}
1079 // Reference counted.
1080 virtual ~OriginOperationBase() {
1081 MOZ_ASSERT(mState
== State_Complete
);
1082 MOZ_ASSERT(mActorDestroyed
);
1086 State
GetState() const { return mState
; }
1089 void SetState(State aState
) {
1090 MOZ_ASSERT(mState
== State_Initial
);
1094 void AdvanceState() {
1097 mState
= State_CreatingQuotaManager
;
1099 case State_CreatingQuotaManager
:
1100 mState
= State_DirectoryOpenPending
;
1102 case State_DirectoryOpenPending
:
1103 mState
= State_DirectoryWorkOpen
;
1105 case State_DirectoryWorkOpen
:
1106 mState
= State_UnblockingOpen
;
1108 case State_UnblockingOpen
:
1109 mState
= State_Complete
;
1112 MOZ_CRASH("Bad state!");
1119 virtual void Open() = 0;
1121 nsresult
DirectoryOpen();
1123 virtual nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) = 0;
1125 void Finish(nsresult aResult
);
1127 virtual void UnblockOpen() = 0;
1132 nsresult
FinishInit();
1134 nsresult
QuotaManagerOpen();
1136 nsresult
DirectoryWork();
1139 class FinalizeOriginEvictionOp
: public OriginOperationBase
{
1140 nsTArray
<RefPtr
<OriginDirectoryLock
>> mLocks
;
1143 FinalizeOriginEvictionOp(nsIEventTarget
* aBackgroundThread
,
1144 nsTArray
<RefPtr
<OriginDirectoryLock
>>&& aLocks
)
1145 : OriginOperationBase(aBackgroundThread
), mLocks(std::move(aLocks
)) {
1146 MOZ_ASSERT(!NS_IsMainThread());
1151 void RunOnIOThreadImmediately();
1154 ~FinalizeOriginEvictionOp() = default;
1156 virtual void Open() override
;
1158 virtual nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1160 virtual void UnblockOpen() override
;
1163 class NormalOriginOperationBase
1164 : public OriginOperationBase
,
1165 public OpenDirectoryListener
,
1166 public SupportsCheckedUnsafePtr
<CheckIf
<DiagnosticAssertEnabled
>> {
1167 RefPtr
<DirectoryLock
> mDirectoryLock
;
1170 Nullable
<PersistenceType
> mPersistenceType
;
1171 OriginScope mOriginScope
;
1172 Nullable
<Client::Type
> mClientType
;
1173 mozilla::Atomic
<bool> mCanceled
;
1174 const bool mExclusive
;
1175 bool mNeedsDirectoryLocking
;
1178 void RunImmediately() {
1179 MOZ_ASSERT(GetState() == State_Initial
);
1181 MOZ_ALWAYS_SUCCEEDS(this->Run());
1185 NormalOriginOperationBase(const Nullable
<PersistenceType
>& aPersistenceType
,
1186 const OriginScope
& aOriginScope
, bool aExclusive
)
1187 : mPersistenceType(aPersistenceType
),
1188 mOriginScope(aOriginScope
),
1189 mExclusive(aExclusive
),
1190 mNeedsDirectoryLocking(true) {
1191 AssertIsOnOwningThread();
1194 ~NormalOriginOperationBase() = default;
1197 // Need to declare refcounting unconditionally, because
1198 // OpenDirectoryListener has pure-virtual refcounting.
1199 NS_DECL_ISUPPORTS_INHERITED
1201 virtual void Open() override
;
1203 virtual void UnblockOpen() override
;
1205 // OpenDirectoryListener overrides.
1206 virtual void DirectoryLockAcquired(DirectoryLock
* aLock
) override
;
1208 virtual void DirectoryLockFailed() override
;
1210 // Used to send results before unblocking open.
1211 virtual void SendResults() = 0;
1214 class SaveOriginAccessTimeOp
: public NormalOriginOperationBase
{
1218 SaveOriginAccessTimeOp(PersistenceType aPersistenceType
,
1219 const nsACString
& aOrigin
, int64_t aTimestamp
)
1220 : NormalOriginOperationBase(Nullable
<PersistenceType
>(aPersistenceType
),
1221 OriginScope::FromOrigin(aOrigin
),
1222 /* aExclusive */ false),
1223 mTimestamp(aTimestamp
) {
1224 AssertIsOnOwningThread();
1228 ~SaveOriginAccessTimeOp() = default;
1230 virtual nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1232 virtual void SendResults() override
;
1235 /*******************************************************************************
1236 * Actor class declarations
1237 ******************************************************************************/
1239 class Quota final
: public PQuotaParent
{
1241 bool mActorDestroyed
;
1247 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota
)
1252 void StartIdleMaintenance();
1254 bool VerifyRequestParams(const UsageRequestParams
& aParams
) const;
1256 bool VerifyRequestParams(const RequestParams
& aParams
) const;
1259 virtual void ActorDestroy(ActorDestroyReason aWhy
) override
;
1261 virtual PQuotaUsageRequestParent
* AllocPQuotaUsageRequestParent(
1262 const UsageRequestParams
& aParams
) override
;
1264 virtual mozilla::ipc::IPCResult
RecvPQuotaUsageRequestConstructor(
1265 PQuotaUsageRequestParent
* aActor
,
1266 const UsageRequestParams
& aParams
) override
;
1268 virtual bool DeallocPQuotaUsageRequestParent(
1269 PQuotaUsageRequestParent
* aActor
) override
;
1271 virtual PQuotaRequestParent
* AllocPQuotaRequestParent(
1272 const RequestParams
& aParams
) override
;
1274 virtual mozilla::ipc::IPCResult
RecvPQuotaRequestConstructor(
1275 PQuotaRequestParent
* aActor
, const RequestParams
& aParams
) override
;
1277 virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent
* aActor
) override
;
1279 virtual mozilla::ipc::IPCResult
RecvStartIdleMaintenance() override
;
1281 virtual mozilla::ipc::IPCResult
RecvStopIdleMaintenance() override
;
1283 virtual mozilla::ipc::IPCResult
RecvAbortOperationsForProcess(
1284 const ContentParentId
& aContentParentId
) override
;
1287 class QuotaUsageRequestBase
: public NormalOriginOperationBase
,
1288 public PQuotaUsageRequestParent
{
1290 // May be overridden by subclasses if they need to perform work on the
1291 // background thread before being run.
1292 virtual void Init(Quota
& aQuota
);
1295 QuotaUsageRequestBase()
1296 : NormalOriginOperationBase(Nullable
<PersistenceType
>(),
1297 OriginScope::FromNull(),
1298 /* aExclusive */ false) {}
1300 mozilla::Result
<UsageInfo
, nsresult
> GetUsageForOrigin(
1301 QuotaManager
& aQuotaManager
, PersistenceType aPersistenceType
,
1302 const OriginMetadata
& aOriginMetadata
);
1304 // Subclasses use this override to set the IPDL response value.
1305 virtual void GetResponse(UsageRequestResponse
& aResponse
) = 0;
1308 mozilla::Result
<UsageInfo
, nsresult
> GetUsageForOriginEntries(
1309 QuotaManager
& aQuotaManager
, PersistenceType aPersistenceType
,
1310 const OriginMetadata
& aOriginMetadata
, nsIFile
& aDirectory
,
1313 void SendResults() override
;
1316 void ActorDestroy(ActorDestroyReason aWhy
) override
;
1318 mozilla::ipc::IPCResult
RecvCancel() final
;
1321 // A mix-in class to simplify operations that need to process every origin in
1322 // one or more repositories. Sub-classes should call TraverseRepository in their
1323 // DoDirectoryWork and implement a ProcessOrigin method for their per-origin
1325 class TraverseRepositoryHelper
{
1327 TraverseRepositoryHelper() = default;
1330 virtual ~TraverseRepositoryHelper() = default;
1332 // If ProcessOrigin returns an error, TraverseRepository will immediately
1333 // terminate and return the received error code to its caller.
1334 nsresult
TraverseRepository(QuotaManager
& aQuotaManager
,
1335 PersistenceType aPersistenceType
);
1338 virtual const Atomic
<bool>& GetIsCanceledFlag() = 0;
1340 virtual nsresult
ProcessOrigin(QuotaManager
& aQuotaManager
,
1341 nsIFile
& aOriginDir
, const bool aPersistent
,
1342 const PersistenceType aPersistenceType
) = 0;
1345 class GetUsageOp final
: public QuotaUsageRequestBase
,
1346 public TraverseRepositoryHelper
{
1347 nsTArray
<OriginUsage
> mOriginUsages
;
1348 nsTHashMap
<nsCStringHashKey
, uint32_t> mOriginUsagesIndex
;
1353 explicit GetUsageOp(const UsageRequestParams
& aParams
);
1356 ~GetUsageOp() = default;
1358 void ProcessOriginInternal(QuotaManager
* aQuotaManager
,
1359 const PersistenceType aPersistenceType
,
1360 const nsACString
& aOrigin
,
1361 const int64_t aTimestamp
, const bool aPersisted
,
1362 const uint64_t aUsage
);
1364 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1366 const Atomic
<bool>& GetIsCanceledFlag() override
;
1368 nsresult
ProcessOrigin(QuotaManager
& aQuotaManager
, nsIFile
& aOriginDir
,
1369 const bool aPersistent
,
1370 const PersistenceType aPersistenceType
) override
;
1372 void GetResponse(UsageRequestResponse
& aResponse
) override
;
1375 class GetOriginUsageOp final
: public QuotaUsageRequestBase
{
1379 uint64_t mFileUsage
;
1383 explicit GetOriginUsageOp(const UsageRequestParams
& aParams
);
1386 ~GetOriginUsageOp() = default;
1388 virtual nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1390 void GetResponse(UsageRequestResponse
& aResponse
) override
;
1393 class QuotaRequestBase
: public NormalOriginOperationBase
,
1394 public PQuotaRequestParent
{
1396 // May be overridden by subclasses if they need to perform work on the
1397 // background thread before being run.
1398 virtual void Init(Quota
& aQuota
);
1401 explicit QuotaRequestBase(bool aExclusive
)
1402 : NormalOriginOperationBase(Nullable
<PersistenceType
>(),
1403 OriginScope::FromNull(), aExclusive
) {}
1405 // Subclasses use this override to set the IPDL response value.
1406 virtual void GetResponse(RequestResponse
& aResponse
) = 0;
1409 virtual void SendResults() override
;
1412 virtual void ActorDestroy(ActorDestroyReason aWhy
) override
;
1415 class StorageNameOp final
: public QuotaRequestBase
{
1421 void Init(Quota
& aQuota
) override
;
1424 ~StorageNameOp() = default;
1426 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1428 void GetResponse(RequestResponse
& aResponse
) override
;
1431 class InitializedRequestBase
: public QuotaRequestBase
{
1436 void Init(Quota
& aQuota
) override
;
1439 InitializedRequestBase();
1442 class StorageInitializedOp final
: public InitializedRequestBase
{
1444 ~StorageInitializedOp() = default;
1446 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1448 void GetResponse(RequestResponse
& aResponse
) override
;
1451 class TemporaryStorageInitializedOp final
: public InitializedRequestBase
{
1453 ~TemporaryStorageInitializedOp() = default;
1455 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1457 void GetResponse(RequestResponse
& aResponse
) override
;
1460 class InitOp final
: public QuotaRequestBase
{
1464 void Init(Quota
& aQuota
) override
;
1467 ~InitOp() = default;
1469 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1471 void GetResponse(RequestResponse
& aResponse
) override
;
1474 class InitTemporaryStorageOp final
: public QuotaRequestBase
{
1476 InitTemporaryStorageOp();
1478 void Init(Quota
& aQuota
) override
;
1481 ~InitTemporaryStorageOp() = default;
1483 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1485 void GetResponse(RequestResponse
& aResponse
) override
;
1488 class InitializeOriginRequestBase
: public QuotaRequestBase
{
1495 void Init(Quota
& aQuota
) override
;
1498 InitializeOriginRequestBase(PersistenceType aPersistenceType
,
1499 const PrincipalInfo
& aPrincipalInfo
);
1502 class InitializePersistentOriginOp final
: public InitializeOriginRequestBase
{
1504 explicit InitializePersistentOriginOp(const RequestParams
& aParams
);
1507 ~InitializePersistentOriginOp() = default;
1509 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1511 void GetResponse(RequestResponse
& aResponse
) override
;
1514 class InitializeTemporaryOriginOp final
: public InitializeOriginRequestBase
{
1516 explicit InitializeTemporaryOriginOp(const RequestParams
& aParams
);
1519 ~InitializeTemporaryOriginOp() = default;
1521 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1523 void GetResponse(RequestResponse
& aResponse
) override
;
1526 class ResetOrClearOp final
: public QuotaRequestBase
{
1530 explicit ResetOrClearOp(bool aClear
);
1532 void Init(Quota
& aQuota
) override
;
1535 ~ResetOrClearOp() = default;
1537 void DeleteFiles(QuotaManager
& aQuotaManager
);
1539 void DeleteStorageFile(QuotaManager
& aQuotaManager
);
1541 virtual nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1543 virtual void GetResponse(RequestResponse
& aResponse
) override
;
1546 class ClearRequestBase
: public QuotaRequestBase
{
1548 explicit ClearRequestBase(bool aExclusive
) : QuotaRequestBase(aExclusive
) {
1549 AssertIsOnOwningThread();
1552 void DeleteFiles(QuotaManager
& aQuotaManager
,
1553 PersistenceType aPersistenceType
);
1555 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1558 class ClearOriginOp final
: public ClearRequestBase
{
1559 const ClearResetOriginParams mParams
;
1560 const bool mMatchAll
;
1563 explicit ClearOriginOp(const RequestParams
& aParams
);
1565 void Init(Quota
& aQuota
) override
;
1568 ~ClearOriginOp() = default;
1570 void GetResponse(RequestResponse
& aResponse
) override
;
1573 class ClearDataOp final
: public ClearRequestBase
{
1574 const ClearDataParams mParams
;
1577 explicit ClearDataOp(const RequestParams
& aParams
);
1579 void Init(Quota
& aQuota
) override
;
1582 ~ClearDataOp() = default;
1584 void GetResponse(RequestResponse
& aResponse
) override
;
1587 class ResetOriginOp final
: public QuotaRequestBase
{
1589 explicit ResetOriginOp(const RequestParams
& aParams
);
1591 void Init(Quota
& aQuota
) override
;
1594 ~ResetOriginOp() = default;
1596 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1598 void GetResponse(RequestResponse
& aResponse
) override
;
1601 class PersistRequestBase
: public QuotaRequestBase
{
1602 const PrincipalInfo mPrincipalInfo
;
1609 void Init(Quota
& aQuota
) override
;
1612 explicit PersistRequestBase(const PrincipalInfo
& aPrincipalInfo
);
1615 class PersistedOp final
: public PersistRequestBase
{
1619 explicit PersistedOp(const RequestParams
& aParams
);
1622 ~PersistedOp() = default;
1624 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1626 void GetResponse(RequestResponse
& aResponse
) override
;
1629 class PersistOp final
: public PersistRequestBase
{
1631 explicit PersistOp(const RequestParams
& aParams
);
1634 ~PersistOp() = default;
1636 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1638 void GetResponse(RequestResponse
& aResponse
) override
;
1641 class EstimateOp final
: public QuotaRequestBase
{
1647 explicit EstimateOp(const RequestParams
& aParams
);
1650 ~EstimateOp() = default;
1652 virtual nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1654 void GetResponse(RequestResponse
& aResponse
) override
;
1657 class ListOriginsOp final
: public QuotaRequestBase
,
1658 public TraverseRepositoryHelper
{
1659 // XXX Bug 1521541 will make each origin has it's own state.
1660 nsTArray
<nsCString
> mOrigins
;
1665 void Init(Quota
& aQuota
) override
;
1668 ~ListOriginsOp() = default;
1670 nsresult
DoDirectoryWork(QuotaManager
& aQuotaManager
) override
;
1672 const Atomic
<bool>& GetIsCanceledFlag() override
;
1674 nsresult
ProcessOrigin(QuotaManager
& aQuotaManager
, nsIFile
& aOriginDir
,
1675 const bool aPersistent
,
1676 const PersistenceType aPersistenceType
) override
;
1678 void GetResponse(RequestResponse
& aResponse
) override
;
1681 /*******************************************************************************
1682 * Other class declarations
1683 ******************************************************************************/
1685 class StoragePressureRunnable final
: public Runnable
{
1686 const uint64_t mUsage
;
1689 explicit StoragePressureRunnable(uint64_t aUsage
)
1690 : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
1694 ~StoragePressureRunnable() = default;
1699 class RecordQuotaInfoLoadTimeHelper final
: public Runnable
{
1700 // TimeStamps that are set on the IO thread.
1701 LazyInitializedOnceNotNull
<const TimeStamp
> mStartTime
;
1702 LazyInitializedOnceNotNull
<const TimeStamp
> mEndTime
;
1704 // A TimeStamp that is set on the main thread.
1705 LazyInitializedOnceNotNull
<const TimeStamp
> mInitializedTime
;
1708 RecordQuotaInfoLoadTimeHelper()
1709 : Runnable("dom::quota::RecordQuotaInfoLoadTimeHelper") {}
1716 ~RecordQuotaInfoLoadTimeHelper() = default;
1721 /*******************************************************************************
1723 ******************************************************************************/
1725 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
1727 class PrincipalVerifier final
: public Runnable
{
1728 nsTArray
<PrincipalInfo
> mPrincipalInfos
;
1731 static already_AddRefed
<PrincipalVerifier
> CreateAndDispatch(
1732 nsTArray
<PrincipalInfo
>&& aPrincipalInfos
);
1735 explicit PrincipalVerifier(nsTArray
<PrincipalInfo
>&& aPrincipalInfos
)
1736 : Runnable("dom::quota::PrincipalVerifier"),
1737 mPrincipalInfos(std::move(aPrincipalInfos
)) {
1738 AssertIsOnIOThread();
1741 virtual ~PrincipalVerifier() = default;
1743 Result
<Ok
, nsCString
> CheckPrincipalInfoValidity(
1744 const PrincipalInfo
& aPrincipalInfo
);
1751 /*******************************************************************************
1753 ******************************************************************************/
1755 template <typename T
, bool = std::is_unsigned_v
<T
>>
1757 static void Assert(T aInt
) {
1758 static_assert(std::is_integral_v
<T
>, "Not an integer!");
1759 MOZ_ASSERT(aInt
>= 0);
1763 template <typename T
>
1764 struct IntChecker
<T
, true> {
1765 static void Assert(T aInt
) {
1766 static_assert(std::is_integral_v
<T
>, "Not an integer!");
1770 template <typename T
>
1771 void AssertNoOverflow(uint64_t aDest
, T aArg
) {
1772 IntChecker
<T
>::Assert(aDest
);
1773 IntChecker
<T
>::Assert(aArg
);
1774 MOZ_ASSERT(UINT64_MAX
- aDest
>= uint64_t(aArg
));
1777 template <typename T
, typename U
>
1778 void AssertNoUnderflow(T aDest
, U aArg
) {
1779 IntChecker
<T
>::Assert(aDest
);
1780 IntChecker
<T
>::Assert(aArg
);
1781 MOZ_ASSERT(uint64_t(aDest
) >= uint64_t(aArg
));
1784 inline bool IsDotFile(const nsAString
& aFileName
) {
1785 return QuotaManager::IsDotFile(aFileName
);
1788 inline bool IsOSMetadata(const nsAString
& aFileName
) {
1789 return QuotaManager::IsOSMetadata(aFileName
);
1792 bool IsOriginMetadata(const nsAString
& aFileName
) {
1793 return aFileName
.EqualsLiteral(METADATA_FILE_NAME
) ||
1794 aFileName
.EqualsLiteral(METADATA_V2_FILE_NAME
) ||
1795 IsOSMetadata(aFileName
);
1798 bool IsTempMetadata(const nsAString
& aFileName
) {
1799 return aFileName
.EqualsLiteral(METADATA_TMP_FILE_NAME
) ||
1800 aFileName
.EqualsLiteral(METADATA_V2_TMP_FILE_NAME
);
1803 // Return whether the group was actually updated.
1804 Result
<bool, nsresult
> MaybeUpdateGroupForOrigin(
1805 OriginMetadata
& aOriginMetadata
) {
1806 MOZ_ASSERT(!NS_IsMainThread());
1808 bool updated
= false;
1810 if (aOriginMetadata
.mOrigin
.EqualsLiteral(kChromeOrigin
)) {
1811 if (!aOriginMetadata
.mGroup
.EqualsLiteral(kChromeOrigin
)) {
1812 aOriginMetadata
.mGroup
.AssignLiteral(kChromeOrigin
);
1816 OriginAttributes originAttributes
;
1817 nsCString originNoSuffix
;
1818 QM_TRY(OkIf(originAttributes
.PopulateFromOrigin(aOriginMetadata
.mOrigin
,
1820 Err(NS_ERROR_FAILURE
));
1823 QM_TRY(MozURL::Init(getter_AddRefs(url
), originNoSuffix
), QM_PROPAGATE
,
1824 [&originNoSuffix
](const nsresult
) {
1825 QM_WARNING("A URL %s is not recognized by MozURL",
1826 originNoSuffix
.get());
1829 QM_TRY_INSPECT(const auto& baseDomain
,
1830 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoCString
, *url
, BaseDomain
));
1832 const nsCString upToDateGroup
= baseDomain
+ aOriginMetadata
.mSuffix
;
1834 if (aOriginMetadata
.mGroup
!= upToDateGroup
) {
1835 aOriginMetadata
.mGroup
= upToDateGroup
;
1838 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
1839 ContentPrincipalInfo contentPrincipalInfo
;
1840 contentPrincipalInfo
.attrs() = originAttributes
;
1841 contentPrincipalInfo
.originNoSuffix() = originNoSuffix
;
1842 contentPrincipalInfo
.spec() = originNoSuffix
;
1843 contentPrincipalInfo
.baseDomain() = baseDomain
;
1845 PrincipalInfo
principalInfo(contentPrincipalInfo
);
1847 nsTArray
<PrincipalInfo
> principalInfos
;
1848 principalInfos
.AppendElement(principalInfo
);
1850 RefPtr
<PrincipalVerifier
> principalVerifier
=
1851 PrincipalVerifier::CreateAndDispatch(std::move(principalInfos
));
1861 BackgroundThreadObject::BackgroundThreadObject()
1862 : mOwningThread(GetCurrentEventTarget()) {
1863 AssertIsOnOwningThread();
1866 BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget
* aOwningThread
)
1867 : mOwningThread(aOwningThread
) {}
1871 void BackgroundThreadObject::AssertIsOnOwningThread() const {
1872 AssertIsOnBackgroundThread();
1873 MOZ_ASSERT(mOwningThread
);
1875 MOZ_ASSERT(NS_SUCCEEDED(mOwningThread
->IsOnCurrentThread(¤t
)));
1876 MOZ_ASSERT(current
);
1881 nsIEventTarget
* BackgroundThreadObject::OwningThread() const {
1882 MOZ_ASSERT(mOwningThread
);
1883 return mOwningThread
;
1886 bool IsOnIOThread() {
1887 QuotaManager
* quotaManager
= QuotaManager::Get();
1888 NS_ASSERTION(quotaManager
, "Must have a manager here!");
1891 return NS_SUCCEEDED(
1892 quotaManager
->IOThread()->IsOnCurrentThread(¤tThread
)) &&
1896 void AssertIsOnIOThread() {
1897 NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
1900 void AssertCurrentThreadOwnsQuotaMutex() {
1902 QuotaManager
* quotaManager
= QuotaManager::Get();
1903 NS_ASSERTION(quotaManager
, "Must have a manager here!");
1905 quotaManager
->AssertCurrentThreadOwnsQuotaMutex();
1909 void ReportInternalError(const char* aFile
, uint32_t aLine
, const char* aStr
) {
1910 // Get leaf of file path
1911 for (const char* p
= aFile
; *p
; ++p
) {
1912 if (*p
== '/' && *(p
+ 1)) {
1917 nsContentUtils::LogSimpleConsoleError(
1918 NS_ConvertUTF8toUTF16(
1919 nsPrintfCString("Quota %s: %s:%" PRIu32
, aStr
, aFile
, aLine
)),
1920 "quota", false /* Quota Manager is not active in private browsing mode */,
1921 true /* Quota Manager runs always in a chrome context */);
1926 bool gInvalidateQuotaCache
= false;
1927 StaticAutoPtr
<nsString
> gBasePath
;
1928 StaticAutoPtr
<nsString
> gStorageName
;
1929 StaticAutoPtr
<nsCString
> gBuildId
;
1932 bool gQuotaManagerInitialized
= false;
1935 StaticRefPtr
<QuotaManager
> gInstance
;
1936 mozilla::Atomic
<bool> gShutdown(false);
1938 // A time stamp that can only be accessed on the main thread.
1939 TimeStamp gLastOSWake
;
1941 typedef nsTArray
<CheckedUnsafePtr
<NormalOriginOperationBase
>>
1942 NormalOriginOpArray
;
1943 StaticAutoPtr
<NormalOriginOpArray
> gNormalOriginOps
;
1945 // Constants for temporary storage limit computing.
1946 static const uint32_t kDefaultChunkSizeKB
= 10 * 1024;
1948 void RegisterNormalOriginOp(NormalOriginOperationBase
& aNormalOriginOp
) {
1949 AssertIsOnBackgroundThread();
1951 if (!gNormalOriginOps
) {
1952 gNormalOriginOps
= new NormalOriginOpArray();
1955 gNormalOriginOps
->AppendElement(&aNormalOriginOp
);
1958 void UnregisterNormalOriginOp(NormalOriginOperationBase
& aNormalOriginOp
) {
1959 AssertIsOnBackgroundThread();
1960 MOZ_ASSERT(gNormalOriginOps
);
1962 gNormalOriginOps
->RemoveElement(&aNormalOriginOp
);
1964 if (gNormalOriginOps
->IsEmpty()) {
1965 gNormalOriginOps
= nullptr;
1969 class StorageOperationBase
{
1971 struct OriginProps
{
1972 enum Type
{ eChrome
, eContent
, eObsolete
, eInvalid
};
1974 NotNull
<nsCOMPtr
<nsIFile
>> mDirectory
;
1977 OriginAttributes mAttrs
;
1979 OriginMetadata mOriginMetadata
;
1980 nsCString mOriginalSuffix
;
1982 LazyInitializedOnceEarlyDestructible
<const PersistenceType
>
1986 bool mNeedsRestore2
;
1990 explicit OriginProps(MovingNotNull
<nsCOMPtr
<nsIFile
>> aDirectory
)
1991 : mDirectory(std::move(aDirectory
)),
1994 mNeedsRestore(false),
1995 mNeedsRestore2(false),
1998 template <typename PersistenceTypeFunc
>
1999 nsresult
Init(PersistenceTypeFunc
&& aPersistenceTypeFunc
);
2002 nsTArray
<OriginProps
> mOriginProps
;
2004 nsCOMPtr
<nsIFile
> mDirectory
;
2007 explicit StorageOperationBase(nsIFile
* aDirectory
) : mDirectory(aDirectory
) {
2008 AssertIsOnIOThread();
2011 NS_INLINE_DECL_REFCOUNTING(StorageOperationBase
)
2014 virtual ~StorageOperationBase() = default;
2016 nsresult
GetDirectoryMetadata(nsIFile
* aDirectory
, int64_t& aTimestamp
,
2017 nsACString
& aGroup
, nsACString
& aOrigin
,
2018 Nullable
<bool>& aIsApp
);
2020 // Upgrade helper to load the contents of ".metadata-v2" files from previous
2021 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
2022 // method, it is only intended to read current version ".metadata-v2" files.
2023 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
2024 // because our "storage.sqlite" lets us track the overall version of the
2025 // storage directory.
2026 nsresult
GetDirectoryMetadata2(nsIFile
* aDirectory
, int64_t& aTimestamp
,
2027 nsACString
& aSuffix
, nsACString
& aGroup
,
2028 nsACString
& aOrigin
, bool& aIsApp
);
2030 int64_t GetOriginLastModifiedTime(const OriginProps
& aOriginProps
);
2032 nsresult
RemoveObsoleteOrigin(const OriginProps
& aOriginProps
);
2035 * Rename the origin if the origin string generation from nsIPrincipal
2036 * changed. This consists of renaming the origin in the metadata files and
2037 * renaming the origin directory itself. For simplicity, the origin in
2038 * metadata files is not actually updated, but the metadata files are
2039 * recreated instead.
2041 * @param aOriginProps the properties of the origin to check.
2043 * @return whether origin was renamed.
2045 Result
<bool, nsresult
> MaybeRenameOrigin(const OriginProps
& aOriginProps
);
2047 nsresult
ProcessOriginDirectories();
2049 virtual nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) = 0;
2052 class MOZ_STACK_CLASS OriginParser final
{
2054 enum ResultType
{ InvalidOrigin
, ObsoleteOrigin
, ValidOrigin
};
2058 nsCCharSeparatedTokenizerTemplate
<NS_TokenizerIgnoreNothing
>;
2060 enum SchemeType
{ eNone
, eFile
, eAbout
, eChrome
};
2063 eExpectingAppIdOrScheme
,
2064 eExpectingInMozBrowser
,
2066 eExpectingEmptyToken1
,
2067 eExpectingEmptyToken2
,
2068 eExpectingEmptyTokenOrUniversalFileOrigin
,
2071 eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
,
2072 eExpectingEmptyTokenOrPathnameComponent
,
2073 eExpectingEmptyToken1OrHost
,
2075 // We transit from eExpectingHost to this state when we encounter a host
2076 // beginning with "[" which indicates an IPv6 literal. Because we mangle the
2077 // IPv6 ":" delimiter to be a "+", we will receive separate tokens for each
2078 // portion of the IPv6 address, including a final token that ends with "]".
2079 // (Note that we do not mangle "[" or "]".) Note that the URL spec
2080 // explicitly disclaims support for "<zone_id>" and so we don't have to deal
2082 eExpectingIPV6Token
,
2084 eHandledTrailingSeparator
2087 const nsCString mOrigin
;
2088 Tokenizer mTokenizer
;
2092 Nullable
<uint32_t> mPort
;
2093 nsTArray
<nsCString
> mPathnameComponents
;
2094 nsCString mHandledTokens
;
2096 SchemeType mSchemeType
;
2098 bool mInIsolatedMozBrowser
;
2099 bool mUniversalFileOrigin
;
2100 bool mMaybeDriveLetter
;
2102 bool mMaybeObsolete
;
2104 // Number of group which a IPv6 address has. Should be less than 9.
2108 explicit OriginParser(const nsACString
& aOrigin
)
2110 mTokenizer(aOrigin
, '+'),
2113 mState(eExpectingAppIdOrScheme
),
2114 mInIsolatedMozBrowser(false),
2115 mUniversalFileOrigin(false),
2116 mMaybeDriveLetter(false),
2118 mMaybeObsolete(false),
2121 static ResultType
ParseOrigin(const nsACString
& aOrigin
, nsCString
& aSpec
,
2122 OriginAttributes
* aAttrs
,
2123 nsCString
& aOriginalSuffix
);
2125 ResultType
Parse(nsACString
& aSpec
);
2128 void HandleScheme(const nsDependentCSubstring
& aToken
);
2130 void HandlePathnameComponent(const nsDependentCSubstring
& aToken
);
2132 void HandleToken(const nsDependentCSubstring
& aToken
);
2134 void HandleTrailingSeparator();
2137 class RepositoryOperationBase
: public StorageOperationBase
{
2139 explicit RepositoryOperationBase(nsIFile
* aDirectory
)
2140 : StorageOperationBase(aDirectory
) {}
2142 nsresult
ProcessRepository();
2145 virtual ~RepositoryOperationBase() = default;
2147 template <typename UpgradeMethod
>
2148 nsresult
MaybeUpgradeClients(const OriginProps
& aOriginsProps
,
2149 UpgradeMethod aMethod
);
2152 virtual PersistenceType
PersistenceTypeFromSpec(const nsCString
& aSpec
) = 0;
2154 virtual nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
2155 bool* aRemoved
) = 0;
2157 virtual nsresult
PrepareClientDirectory(nsIFile
* aFile
,
2158 const nsAString
& aLeafName
,
2162 class CreateOrUpgradeDirectoryMetadataHelper final
2163 : public RepositoryOperationBase
{
2164 nsCOMPtr
<nsIFile
> mPermanentStorageDir
;
2166 // The legacy PersistenceType, before the default repository introduction.
2167 enum class LegacyPersistenceType
{
2170 // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
2174 LazyInitializedOnce
<const LegacyPersistenceType
> mLegacyPersistenceType
;
2177 explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile
* aDirectory
)
2178 : RepositoryOperationBase(aDirectory
) {}
2183 Maybe
<LegacyPersistenceType
> LegacyPersistenceTypeFromFile(nsIFile
& aFile
,
2186 PersistenceType
PersistenceTypeFromLegacyPersistentSpec(
2187 const nsCString
& aSpec
);
2189 PersistenceType
PersistenceTypeFromSpec(const nsCString
& aSpec
) override
;
2191 nsresult
MaybeUpgradeOriginDirectory(nsIFile
* aDirectory
);
2193 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
2194 bool* aRemoved
) override
;
2196 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
2199 class UpgradeStorageHelperBase
: public RepositoryOperationBase
{
2200 LazyInitializedOnce
<const PersistenceType
> mPersistenceType
;
2203 explicit UpgradeStorageHelperBase(nsIFile
* aDirectory
)
2204 : RepositoryOperationBase(aDirectory
) {}
2209 PersistenceType
PersistenceTypeFromSpec(const nsCString
& aSpec
) override
;
2212 class UpgradeStorageFrom0_0To1_0Helper final
: public UpgradeStorageHelperBase
{
2214 explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile
* aDirectory
)
2215 : UpgradeStorageHelperBase(aDirectory
) {}
2218 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
2219 bool* aRemoved
) override
;
2221 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
2224 class UpgradeStorageFrom1_0To2_0Helper final
: public UpgradeStorageHelperBase
{
2226 explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile
* aDirectory
)
2227 : UpgradeStorageHelperBase(aDirectory
) {}
2230 nsresult
MaybeRemoveMorgueDirectory(const OriginProps
& aOriginProps
);
2233 * Remove the origin directory if appId is present in origin attributes.
2235 * @param aOriginProps the properties of the origin to check.
2237 * @return whether the origin directory was removed.
2239 Result
<bool, nsresult
> MaybeRemoveAppsData(const OriginProps
& aOriginProps
);
2241 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
2242 bool* aRemoved
) override
;
2244 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
2247 class UpgradeStorageFrom2_0To2_1Helper final
: public UpgradeStorageHelperBase
{
2249 explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile
* aDirectory
)
2250 : UpgradeStorageHelperBase(aDirectory
) {}
2253 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
2254 bool* aRemoved
) override
;
2256 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
2259 class UpgradeStorageFrom2_1To2_2Helper final
: public UpgradeStorageHelperBase
{
2261 explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile
* aDirectory
)
2262 : UpgradeStorageHelperBase(aDirectory
) {}
2265 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
2266 bool* aRemoved
) override
;
2268 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
2270 nsresult
PrepareClientDirectory(nsIFile
* aFile
, const nsAString
& aLeafName
,
2271 bool& aRemoved
) override
;
2274 class RestoreDirectoryMetadata2Helper final
: public StorageOperationBase
{
2275 LazyInitializedOnce
<const PersistenceType
> mPersistenceType
;
2278 explicit RestoreDirectoryMetadata2Helper(nsIFile
* aDirectory
)
2279 : StorageOperationBase(aDirectory
) {}
2283 nsresult
RestoreMetadata2File();
2286 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
2289 auto MakeSanitizedOriginCString(const nsACString
& aOrigin
) {
2291 NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars
,
2292 FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR
),
2293 "Illegal file characters have changed!");
2296 nsAutoCString res
{aOrigin
};
2298 res
.ReplaceChar(QuotaManager::kReplaceChars
, '+');
2303 auto MakeSanitizedOriginString(const nsACString
& aOrigin
) {
2304 // An origin string is ASCII-only, since it is obtained via
2305 // nsIPrincipal::GetOrigin, which returns an ACString.
2306 return NS_ConvertASCIItoUTF16(MakeSanitizedOriginCString(aOrigin
));
2309 Result
<nsAutoString
, nsresult
> GetPathForStorage(
2310 nsIFile
& aBaseDir
, const nsAString
& aStorageName
) {
2311 QM_TRY_INSPECT(const auto& storageDir
,
2312 CloneFileAndAppend(aBaseDir
, aStorageName
));
2314 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, storageDir
, GetPath
));
2317 int64_t GetLastModifiedTime(PersistenceType aPersistenceType
, nsIFile
& aFile
) {
2318 AssertIsOnIOThread();
2320 class MOZ_STACK_CLASS Helper final
{
2322 static nsresult
GetLastModifiedTime(nsIFile
* aFile
, int64_t* aTimestamp
) {
2323 AssertIsOnIOThread();
2325 MOZ_ASSERT(aTimestamp
);
2327 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*aFile
));
2329 switch (dirEntryKind
) {
2330 case nsIFileKind::ExistsAsDirectory
:
2331 QM_TRY(CollectEachFile(*aFile
,
2332 [&aTimestamp
](const nsCOMPtr
<nsIFile
>& file
)
2333 -> Result
<mozilla::Ok
, nsresult
> {
2335 GetLastModifiedTime(file
, aTimestamp
));
2341 case nsIFileKind::ExistsAsFile
: {
2343 const auto& leafName
,
2344 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, aFile
, GetLeafName
));
2346 // Bug 1595445 will handle unknown files here.
2348 if (IsOriginMetadata(leafName
) || IsTempMetadata(leafName
) ||
2349 IsDotFile(leafName
)) {
2353 QM_TRY_UNWRAP(int64_t timestamp
,
2354 MOZ_TO_RESULT_INVOKE(aFile
, GetLastModifiedTime
));
2356 // Need to convert from milliseconds to microseconds.
2357 MOZ_ASSERT((INT64_MAX
/ PR_USEC_PER_MSEC
) > timestamp
);
2358 timestamp
*= int64_t(PR_USEC_PER_MSEC
);
2360 if (timestamp
> *aTimestamp
) {
2361 *aTimestamp
= timestamp
;
2366 case nsIFileKind::DoesNotExist
:
2367 // Ignore files that got removed externally while iterating.
2375 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
2379 int64_t timestamp
= INT64_MIN
;
2380 nsresult rv
= Helper::GetLastModifiedTime(&aFile
, ×tamp
);
2381 if (NS_FAILED(rv
)) {
2382 timestamp
= PR_Now();
2388 // Returns a bool indicating whether the directory was newly created.
2389 Result
<bool, nsresult
> EnsureDirectory(nsIFile
& aDirectory
) {
2390 AssertIsOnIOThread();
2392 // Callers call this function without checking if the file already exists
2393 // (idempotent usage). QM_OR_ELSE_WARN is not used here since we want to
2394 // ignore NS_ERROR_FILE_ALREADY_EXISTS completely.
2397 MOZ_TO_RESULT_INVOKE(aDirectory
, Create
, nsIFile::DIRECTORY_TYPE
, 0755)
2398 .map([](Ok
) { return false; })
2399 .orElse(ErrToOkOrErr
<NS_ERROR_FILE_ALREADY_EXISTS
, true>));
2402 QM_TRY_INSPECT(const bool& isDirectory
,
2403 MOZ_TO_RESULT_INVOKE(aDirectory
, IsDirectory
));
2404 QM_TRY(OkIf(isDirectory
), Err(NS_ERROR_UNEXPECTED
));
2410 enum FileFlag
{ Truncate
, Update
, Append
};
2412 Result
<nsCOMPtr
<nsIOutputStream
>, nsresult
> GetOutputStream(
2413 nsIFile
& aFile
, FileFlag aFileFlag
) {
2414 AssertIsOnIOThread();
2416 switch (aFileFlag
) {
2417 case FileFlag::Truncate
:
2418 QM_TRY_RETURN(NS_NewLocalFileOutputStream(&aFile
));
2420 case FileFlag::Update
: {
2421 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(&aFile
, Exists
));
2424 return nsCOMPtr
<nsIOutputStream
>();
2427 QM_TRY_INSPECT(const auto& stream
, NS_NewLocalFileStream(&aFile
));
2429 nsCOMPtr
<nsIOutputStream
> outputStream
= do_QueryInterface(stream
);
2430 QM_TRY(OkIf(outputStream
), Err(NS_ERROR_FAILURE
));
2432 return outputStream
;
2435 case FileFlag::Append
:
2436 QM_TRY_RETURN(NS_NewLocalFileOutputStream(
2437 &aFile
, PR_WRONLY
| PR_CREATE_FILE
| PR_APPEND
));
2440 MOZ_CRASH("Should never get here!");
2444 Result
<nsCOMPtr
<nsIBinaryOutputStream
>, nsresult
> GetBinaryOutputStream(
2445 nsIFile
& aFile
, FileFlag aFileFlag
) {
2446 QM_TRY_UNWRAP(auto outputStream
, GetOutputStream(aFile
, aFileFlag
));
2448 QM_TRY(OkIf(outputStream
), Err(NS_ERROR_UNEXPECTED
));
2450 return nsCOMPtr
<nsIBinaryOutputStream
>(
2451 NS_NewObjectOutputStream(outputStream
));
2454 void GetJarPrefix(bool aInIsolatedMozBrowser
, nsACString
& aJarPrefix
) {
2455 aJarPrefix
.Truncate();
2458 if (!aInIsolatedMozBrowser
) {
2462 // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
2464 // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
2465 aJarPrefix
.AppendInt(0); // TODO: this is the appId, to be removed.
2466 aJarPrefix
.Append('+');
2467 aJarPrefix
.Append(aInIsolatedMozBrowser
? 't' : 'f');
2468 aJarPrefix
.Append('+');
2471 nsresult
CreateDirectoryMetadata(nsIFile
& aDirectory
, int64_t aTimestamp
,
2472 const OriginMetadata
& aOriginMetadata
) {
2473 AssertIsOnIOThread();
2475 OriginAttributes groupAttributes
;
2477 nsCString groupNoSuffix
;
2478 QM_TRY(OkIf(groupAttributes
.PopulateFromOrigin(aOriginMetadata
.mGroup
,
2482 nsCString groupPrefix
;
2483 GetJarPrefix(groupAttributes
.mInIsolatedMozBrowser
, groupPrefix
);
2485 nsCString group
= groupPrefix
+ groupNoSuffix
;
2487 OriginAttributes originAttributes
;
2489 nsCString originNoSuffix
;
2490 QM_TRY(OkIf(originAttributes
.PopulateFromOrigin(aOriginMetadata
.mOrigin
,
2494 nsCString originPrefix
;
2495 GetJarPrefix(originAttributes
.mInIsolatedMozBrowser
, originPrefix
);
2497 nsCString origin
= originPrefix
+ originNoSuffix
;
2499 MOZ_ASSERT(groupPrefix
== originPrefix
);
2501 QM_TRY_INSPECT(const auto& file
, MOZ_TO_RESULT_INVOKE_TYPED(
2502 nsCOMPtr
<nsIFile
>, aDirectory
, Clone
));
2504 QM_TRY(file
->Append(nsLiteralString(METADATA_TMP_FILE_NAME
)));
2506 QM_TRY_INSPECT(const auto& stream
,
2507 GetBinaryOutputStream(*file
, FileFlag::Truncate
));
2510 QM_TRY(stream
->Write64(aTimestamp
));
2512 QM_TRY(stream
->WriteStringZ(group
.get()));
2514 QM_TRY(stream
->WriteStringZ(origin
.get()));
2516 // Currently unused (used to be isApp).
2517 QM_TRY(stream
->WriteBoolean(false));
2519 QM_TRY(stream
->Flush());
2521 QM_TRY(stream
->Close());
2523 QM_TRY(file
->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME
)));
2528 nsresult
CreateDirectoryMetadata2(nsIFile
& aDirectory
, int64_t aTimestamp
,
2530 const OriginMetadata
& aOriginMetadata
) {
2531 AssertIsOnIOThread();
2533 QM_TRY_INSPECT(const auto& file
, MOZ_TO_RESULT_INVOKE_TYPED(
2534 nsCOMPtr
<nsIFile
>, aDirectory
, Clone
));
2536 QM_TRY(file
->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME
)));
2538 QM_TRY_INSPECT(const auto& stream
,
2539 GetBinaryOutputStream(*file
, FileFlag::Truncate
));
2542 QM_TRY(stream
->Write64(aTimestamp
));
2544 QM_TRY(stream
->WriteBoolean(aPersisted
));
2547 QM_TRY(stream
->Write32(0));
2550 QM_TRY(stream
->Write32(0));
2552 // The suffix isn't used right now, but we might need it in future. It's
2553 // a bit of redundancy we can live with given how painful is to upgrade
2555 QM_TRY(stream
->WriteStringZ(aOriginMetadata
.mSuffix
.get()));
2557 QM_TRY(stream
->WriteStringZ(aOriginMetadata
.mGroup
.get()));
2559 QM_TRY(stream
->WriteStringZ(aOriginMetadata
.mOrigin
.get()));
2561 // Currently unused (used to be isApp).
2562 QM_TRY(stream
->WriteBoolean(false));
2564 QM_TRY(stream
->Flush());
2566 QM_TRY(stream
->Close());
2568 QM_TRY(file
->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME
)));
2573 Result
<nsCOMPtr
<nsIBinaryInputStream
>, nsresult
> GetBinaryInputStream(
2574 nsIFile
& aDirectory
, const nsAString
& aFilename
) {
2575 MOZ_ASSERT(!NS_IsMainThread());
2577 QM_TRY_INSPECT(const auto& file
, MOZ_TO_RESULT_INVOKE_TYPED(
2578 nsCOMPtr
<nsIFile
>, aDirectory
, Clone
));
2580 QM_TRY(file
->Append(aFilename
));
2582 QM_TRY_UNWRAP(auto stream
, NS_NewLocalFileInputStream(file
));
2584 QM_TRY_INSPECT(const auto& bufferedStream
,
2585 NS_NewBufferedInputStream(stream
.forget(), 512));
2587 QM_TRY(OkIf(bufferedStream
), Err(NS_ERROR_FAILURE
));
2589 return nsCOMPtr
<nsIBinaryInputStream
>(
2590 NS_NewObjectInputStream(bufferedStream
));
2593 // This method computes and returns our best guess for the temporary storage
2594 // limit (in bytes), based on available space.
2595 uint64_t GetTemporaryStorageLimit(uint64_t aAvailableSpaceBytes
) {
2596 // The fixed limit pref can be used to override temporary storage limit
2598 if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
2599 return static_cast<uint64_t>(
2600 StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
2604 uint64_t availableSpaceKB
= aAvailableSpaceBytes
/ 1024;
2606 // Prevent division by zero below.
2607 uint32_t chunkSizeKB
;
2608 if (StaticPrefs::dom_quotaManager_temporaryStorage_chunkSize()) {
2609 chunkSizeKB
= StaticPrefs::dom_quotaManager_temporaryStorage_chunkSize();
2611 chunkSizeKB
= kDefaultChunkSizeKB
;
2614 // Grow/shrink in chunkSizeKB units, deliberately, so that in the common case
2615 // we don't shrink temporary storage and evict origin data every time we
2617 availableSpaceKB
= (availableSpaceKB
/ chunkSizeKB
) * chunkSizeKB
;
2619 // Allow temporary storage to consume up to half the available space.
2620 return availableSpaceKB
* .50 * 1024;
2625 /*******************************************************************************
2626 * Exported functions
2627 ******************************************************************************/
2629 void InitializeQuotaManager() {
2630 MOZ_ASSERT(XRE_IsParentProcess());
2631 MOZ_ASSERT(NS_IsMainThread());
2632 MOZ_ASSERT(!gQuotaManagerInitialized
);
2634 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
2635 ScopedLogExtraInfo::Initialize();
2638 if (!QuotaManager::IsRunningGTests()) {
2639 // This service has to be started on the main thread currently.
2640 const nsCOMPtr
<mozIStorageService
> ss
=
2641 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID
);
2643 QM_WARNONLY_TRY(OkIf(ss
));
2646 QM_WARNONLY_TRY(QuotaManager::Initialize());
2649 gQuotaManagerInitialized
= true;
2653 PQuotaParent
* AllocPQuotaParent() {
2654 AssertIsOnBackgroundThread();
2656 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
2660 auto actor
= MakeRefPtr
<Quota
>();
2662 return actor
.forget().take();
2665 bool DeallocPQuotaParent(PQuotaParent
* aActor
) {
2666 AssertIsOnBackgroundThread();
2669 RefPtr
<Quota
> actor
= dont_AddRef(static_cast<Quota
*>(aActor
));
2673 bool RecvShutdownQuotaManager() {
2674 AssertIsOnBackgroundThread();
2676 QuotaManager::ShutdownInstance();
2681 QuotaManager::Observer
* QuotaManager::Observer::sInstance
= nullptr;
2684 nsresult
QuotaManager::Observer::Initialize() {
2685 MOZ_ASSERT(NS_IsMainThread());
2687 RefPtr
<Observer
> observer
= new Observer();
2689 nsresult rv
= observer
->Init();
2690 if (NS_WARN_IF(NS_FAILED(rv
))) {
2694 sInstance
= observer
;
2700 void QuotaManager::Observer::ShutdownCompleted() {
2701 MOZ_ASSERT(NS_IsMainThread());
2702 MOZ_ASSERT(sInstance
);
2704 sInstance
->mShutdownComplete
= true;
2707 nsresult
QuotaManager::Observer::Init() {
2708 MOZ_ASSERT(NS_IsMainThread());
2710 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2711 if (NS_WARN_IF(!obs
)) {
2712 return NS_ERROR_FAILURE
;
2715 // XXX: Improve the way that we remove observer in failure cases.
2716 nsresult rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
2717 if (NS_WARN_IF(NS_FAILED(rv
))) {
2721 rv
= obs
->AddObserver(this, kProfileDoChangeTopic
, false);
2722 if (NS_WARN_IF(NS_FAILED(rv
))) {
2723 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
2727 rv
= obs
->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
, false);
2728 if (NS_WARN_IF(NS_FAILED(rv
))) {
2729 obs
->RemoveObserver(this, kProfileDoChangeTopic
);
2730 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
2734 rv
= obs
->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC
, false);
2735 if (NS_WARN_IF(NS_FAILED(rv
))) {
2736 obs
->RemoveObserver(this, kProfileDoChangeTopic
);
2737 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
2738 obs
->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
);
2745 nsresult
QuotaManager::Observer::Shutdown() {
2746 MOZ_ASSERT(NS_IsMainThread());
2748 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2749 if (NS_WARN_IF(!obs
)) {
2750 return NS_ERROR_FAILURE
;
2753 MOZ_ALWAYS_SUCCEEDS(obs
->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC
));
2754 MOZ_ALWAYS_SUCCEEDS(
2755 obs
->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
));
2756 MOZ_ALWAYS_SUCCEEDS(obs
->RemoveObserver(this, kProfileDoChangeTopic
));
2757 MOZ_ALWAYS_SUCCEEDS(obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
));
2759 sInstance
= nullptr;
2761 // In general, the instance will have died after the latter removal call, so
2762 // it's not safe to do anything after that point.
2763 // However, Shutdown is currently called from Observe which is called by the
2764 // Observer Service which holds a strong reference to the observer while the
2765 // Observe method is being called.
2770 NS_IMPL_ISUPPORTS(QuotaManager::Observer
, nsIObserver
)
2773 QuotaManager::Observer::Observe(nsISupports
* aSubject
, const char* aTopic
,
2774 const char16_t
* aData
) {
2775 MOZ_ASSERT(NS_IsMainThread());
2779 if (!strcmp(aTopic
, kProfileDoChangeTopic
)) {
2780 if (NS_WARN_IF(gBasePath
)) {
2782 "profile-before-change-qm must precede repeated "
2783 "profile-do-change!");
2787 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns
, true);
2789 gBasePath
= new nsString();
2791 nsCOMPtr
<nsIFile
> baseDir
;
2792 rv
= NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR
,
2793 getter_AddRefs(baseDir
));
2794 if (NS_FAILED(rv
)) {
2795 rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
2796 getter_AddRefs(baseDir
));
2798 if (NS_WARN_IF(NS_FAILED(rv
))) {
2802 rv
= baseDir
->GetPath(*gBasePath
);
2803 if (NS_WARN_IF(NS_FAILED(rv
))) {
2807 gStorageName
= new nsString();
2809 rv
= Preferences::GetString("dom.quotaManager.storageName", *gStorageName
);
2810 if (NS_FAILED(rv
)) {
2811 *gStorageName
= kStorageName
;
2814 gBuildId
= new nsCString();
2816 nsCOMPtr
<nsIPlatformInfo
> platformInfo
=
2817 do_GetService("@mozilla.org/xre/app-info;1");
2818 if (NS_WARN_IF(!platformInfo
)) {
2819 return NS_ERROR_FAILURE
;
2822 rv
= platformInfo
->GetPlatformBuildID(*gBuildId
);
2823 if (NS_WARN_IF(NS_FAILED(rv
))) {
2830 if (!strcmp(aTopic
, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
)) {
2831 if (NS_WARN_IF(!gBasePath
)) {
2832 NS_WARNING("profile-do-change must precede profile-before-change-qm!");
2836 // mPendingProfileChange is our re-entrancy guard (the nested event loop
2837 // below may cause re-entrancy).
2838 if (mPendingProfileChange
) {
2842 AutoRestore
<bool> pending(mPendingProfileChange
);
2843 mPendingProfileChange
= true;
2845 mShutdownComplete
= false;
2847 PBackgroundChild
* backgroundActor
=
2848 BackgroundChild::GetOrCreateForCurrentThread();
2849 if (NS_WARN_IF(!backgroundActor
)) {
2850 return NS_ERROR_FAILURE
;
2853 if (NS_WARN_IF(!backgroundActor
->SendShutdownQuotaManager())) {
2854 return NS_ERROR_FAILURE
;
2857 MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete
; }));
2859 gBasePath
= nullptr;
2861 gStorageName
= nullptr;
2865 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns
, false);
2870 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
2872 if (NS_WARN_IF(NS_FAILED(rv
))) {
2879 if (!strcmp(aTopic
, NS_WIDGET_WAKE_OBSERVER_TOPIC
)) {
2880 gLastOSWake
= TimeStamp::Now();
2885 NS_WARNING("Unknown observer topic!");
2889 /*******************************************************************************
2891 ******************************************************************************/
2893 void QuotaObject::AddRef() {
2894 QuotaManager
* quotaManager
= QuotaManager::Get();
2895 if (!quotaManager
) {
2896 NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2903 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
2908 void QuotaObject::Release() {
2909 QuotaManager
* quotaManager
= QuotaManager::Get();
2910 if (!quotaManager
) {
2911 NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2913 nsrefcnt count
= --mRefCnt
;
2923 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
2932 mOriginInfo
->mQuotaObjects
.Remove(mPath
);
2939 bool QuotaObject::MaybeUpdateSize(int64_t aSize
, bool aTruncate
) {
2940 QuotaManager
* quotaManager
= QuotaManager::Get();
2941 MOZ_ASSERT(quotaManager
);
2943 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
2945 return LockedMaybeUpdateSize(aSize
, aTruncate
);
2948 bool QuotaObject::IncreaseSize(int64_t aDelta
) {
2949 MOZ_ASSERT(aDelta
>= 0);
2951 QuotaManager
* quotaManager
= QuotaManager::Get();
2952 MOZ_ASSERT(quotaManager
);
2954 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
2956 AssertNoOverflow(mSize
, aDelta
);
2957 int64_t size
= mSize
+ aDelta
;
2959 return LockedMaybeUpdateSize(size
, /* aTruncate */ false);
2962 void QuotaObject::DisableQuotaCheck() {
2963 QuotaManager
* quotaManager
= QuotaManager::Get();
2964 MOZ_ASSERT(quotaManager
);
2966 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
2968 mQuotaCheckDisabled
= true;
2971 void QuotaObject::EnableQuotaCheck() {
2972 QuotaManager
* quotaManager
= QuotaManager::Get();
2973 MOZ_ASSERT(quotaManager
);
2975 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
2977 mQuotaCheckDisabled
= false;
2980 bool QuotaObject::LockedMaybeUpdateSize(int64_t aSize
, bool aTruncate
) {
2981 QuotaManager
* quotaManager
= QuotaManager::Get();
2982 MOZ_ASSERT(quotaManager
);
2984 quotaManager
->mQuotaMutex
.AssertCurrentThreadOwns();
2986 if (mWritingDone
== false && mOriginInfo
) {
2987 mWritingDone
= true;
2988 StorageActivityService::SendActivity(mOriginInfo
->mOrigin
);
2991 if (mQuotaCheckDisabled
) {
2995 if (mSize
== aSize
) {
3004 GroupInfo
* groupInfo
= mOriginInfo
->mGroupInfo
;
3005 MOZ_ASSERT(groupInfo
);
3007 if (mSize
> aSize
) {
3009 const int64_t delta
= mSize
- aSize
;
3011 AssertNoUnderflow(quotaManager
->mTemporaryStorageUsage
, delta
);
3012 quotaManager
->mTemporaryStorageUsage
-= delta
;
3014 if (!mOriginInfo
->LockedPersisted()) {
3015 AssertNoUnderflow(groupInfo
->mUsage
, delta
);
3016 groupInfo
->mUsage
-= delta
;
3019 AssertNoUnderflow(mOriginInfo
->mUsage
, delta
);
3020 mOriginInfo
->mUsage
-= delta
;
3022 MOZ_ASSERT(mOriginInfo
->mClientUsages
[mClientType
].isSome());
3023 AssertNoUnderflow(mOriginInfo
->mClientUsages
[mClientType
].value(), delta
);
3024 mOriginInfo
->mClientUsages
[mClientType
] =
3025 Some(mOriginInfo
->mClientUsages
[mClientType
].value() - delta
);
3032 MOZ_ASSERT(mSize
< aSize
);
3034 RefPtr
<GroupInfo
> complementaryGroupInfo
=
3035 groupInfo
->mGroupInfoPair
->LockedGetGroupInfo(
3036 ComplementaryPersistenceType(groupInfo
->mPersistenceType
));
3038 uint64_t delta
= aSize
- mSize
;
3040 AssertNoOverflow(mOriginInfo
->mClientUsages
[mClientType
].valueOr(0), delta
);
3041 uint64_t newClientUsage
=
3042 mOriginInfo
->mClientUsages
[mClientType
].valueOr(0) + delta
;
3044 AssertNoOverflow(mOriginInfo
->mUsage
, delta
);
3045 uint64_t newUsage
= mOriginInfo
->mUsage
+ delta
;
3047 // Temporary storage has no limit for origin usage (there's a group and the
3048 // global limit though).
3050 uint64_t newGroupUsage
= groupInfo
->mUsage
;
3051 if (!mOriginInfo
->LockedPersisted()) {
3052 AssertNoOverflow(groupInfo
->mUsage
, delta
);
3053 newGroupUsage
+= delta
;
3055 uint64_t groupUsage
= groupInfo
->mUsage
;
3056 if (complementaryGroupInfo
) {
3057 AssertNoOverflow(groupUsage
, complementaryGroupInfo
->mUsage
);
3058 groupUsage
+= complementaryGroupInfo
->mUsage
;
3061 // Temporary storage has a hard limit for group usage (20 % of the global
3063 AssertNoOverflow(groupUsage
, delta
);
3064 if (groupUsage
+ delta
> quotaManager
->GetGroupLimit()) {
3069 AssertNoOverflow(quotaManager
->mTemporaryStorageUsage
, delta
);
3070 uint64_t newTemporaryStorageUsage
=
3071 quotaManager
->mTemporaryStorageUsage
+ delta
;
3073 if (newTemporaryStorageUsage
> quotaManager
->mTemporaryStorageLimit
) {
3074 // This will block the thread without holding the lock while waitting.
3076 AutoTArray
<RefPtr
<OriginDirectoryLock
>, 10> locks
;
3077 uint64_t sizeToBeFreed
;
3079 if (IsOnBackgroundThread()) {
3080 MutexAutoUnlock
autoUnlock(quotaManager
->mQuotaMutex
);
3082 sizeToBeFreed
= quotaManager
->CollectOriginsForEviction(delta
, locks
);
3085 quotaManager
->LockedCollectOriginsForEviction(delta
, locks
);
3088 if (!sizeToBeFreed
) {
3089 uint64_t usage
= quotaManager
->mTemporaryStorageUsage
;
3091 MutexAutoUnlock
autoUnlock(quotaManager
->mQuotaMutex
);
3093 quotaManager
->NotifyStoragePressure(usage
);
3098 NS_ASSERTION(sizeToBeFreed
>= delta
, "Huh?");
3101 MutexAutoUnlock
autoUnlock(quotaManager
->mQuotaMutex
);
3103 for (const auto& lock
: locks
) {
3104 quotaManager
->DeleteFilesForOrigin(lock
->GetPersistenceType(),
3111 NS_ASSERTION(mOriginInfo
, "How come?!");
3113 for (const auto& lock
: locks
) {
3114 MOZ_ASSERT(!(lock
->GetPersistenceType() == groupInfo
->mPersistenceType
&&
3115 lock
->Origin() == mOriginInfo
->mOrigin
),
3118 quotaManager
->LockedRemoveQuotaForOrigin(lock
->GetPersistenceType(),
3119 lock
->OriginMetadata());
3122 // We unlocked and relocked several times so we need to recompute all the
3123 // essential variables and recheck the group limit.
3125 AssertNoUnderflow(aSize
, mSize
);
3126 delta
= aSize
- mSize
;
3128 AssertNoOverflow(mOriginInfo
->mClientUsages
[mClientType
].valueOr(0), delta
);
3129 newClientUsage
= mOriginInfo
->mClientUsages
[mClientType
].valueOr(0) + delta
;
3131 AssertNoOverflow(mOriginInfo
->mUsage
, delta
);
3132 newUsage
= mOriginInfo
->mUsage
+ delta
;
3134 newGroupUsage
= groupInfo
->mUsage
;
3135 if (!mOriginInfo
->LockedPersisted()) {
3136 AssertNoOverflow(groupInfo
->mUsage
, delta
);
3137 newGroupUsage
+= delta
;
3139 uint64_t groupUsage
= groupInfo
->mUsage
;
3140 if (complementaryGroupInfo
) {
3141 AssertNoOverflow(groupUsage
, complementaryGroupInfo
->mUsage
);
3142 groupUsage
+= complementaryGroupInfo
->mUsage
;
3145 AssertNoOverflow(groupUsage
, delta
);
3146 if (groupUsage
+ delta
> quotaManager
->GetGroupLimit()) {
3147 // Unfortunately some other thread increased the group usage in the
3148 // meantime and we are not below the group limit anymore.
3150 // However, the origin eviction must be finalized in this case too.
3151 MutexAutoUnlock
autoUnlock(quotaManager
->mQuotaMutex
);
3153 quotaManager
->FinalizeOriginEviction(std::move(locks
));
3159 AssertNoOverflow(quotaManager
->mTemporaryStorageUsage
, delta
);
3160 newTemporaryStorageUsage
= quotaManager
->mTemporaryStorageUsage
+ delta
;
3163 newTemporaryStorageUsage
<= quotaManager
->mTemporaryStorageLimit
,
3166 // Ok, we successfully freed enough space and the operation can continue
3167 // without throwing the quota error.
3168 mOriginInfo
->mClientUsages
[mClientType
] = Some(newClientUsage
);
3170 mOriginInfo
->mUsage
= newUsage
;
3171 if (!mOriginInfo
->LockedPersisted()) {
3172 groupInfo
->mUsage
= newGroupUsage
;
3174 quotaManager
->mTemporaryStorageUsage
= newTemporaryStorageUsage
;
3177 // Some other thread could increase the size in the meantime, but no more
3179 MOZ_ASSERT(mSize
< aSize
);
3182 // Finally, release IO thread only objects and allow next synchronized
3183 // ops for the evicted origins.
3184 MutexAutoUnlock
autoUnlock(quotaManager
->mQuotaMutex
);
3186 quotaManager
->FinalizeOriginEviction(std::move(locks
));
3191 mOriginInfo
->mClientUsages
[mClientType
] = Some(newClientUsage
);
3193 mOriginInfo
->mUsage
= newUsage
;
3194 if (!mOriginInfo
->LockedPersisted()) {
3195 groupInfo
->mUsage
= newGroupUsage
;
3197 quotaManager
->mTemporaryStorageUsage
= newTemporaryStorageUsage
;
3204 /*******************************************************************************
3206 ******************************************************************************/
3208 QuotaManager::QuotaManager(const nsAString
& aBasePath
,
3209 const nsAString
& aStorageName
)
3210 : mQuotaMutex("QuotaManager.mQuotaMutex"),
3211 mBasePath(aBasePath
),
3212 mStorageName(aStorageName
),
3213 mTemporaryStorageUsage(0),
3214 mNextDirectoryLockId(0),
3215 mTemporaryStorageInitialized(false),
3216 mCacheUsable(false) {
3217 AssertIsOnOwningThread();
3218 MOZ_ASSERT(!gInstance
);
3221 QuotaManager::~QuotaManager() {
3222 AssertIsOnOwningThread();
3223 MOZ_ASSERT(!gInstance
|| gInstance
== this);
3227 nsresult
QuotaManager::Initialize() {
3228 MOZ_ASSERT(NS_IsMainThread());
3230 nsresult rv
= Observer::Initialize();
3231 if (NS_WARN_IF(NS_FAILED(rv
))) {
3239 Result
<MovingNotNull
<RefPtr
<QuotaManager
>>, nsresult
>
3240 QuotaManager::GetOrCreate() {
3241 AssertIsOnBackgroundThread();
3244 return WrapMovingNotNullUnchecked(RefPtr
<QuotaManager
>{gInstance
});
3247 QM_TRY(OkIf(gBasePath
), Err(NS_ERROR_FAILURE
), [](const auto&) {
3249 "Trying to create QuotaManager before profile-do-change! "
3250 "Forgot to call do_get_profile()?");
3253 QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE
), [](const auto&) {
3255 "Trying to create QuotaManager after profile-before-change-qm!");
3258 auto instance
= MakeRefPtr
<QuotaManager
>(*gBasePath
, *gStorageName
);
3260 QM_TRY(instance
->Init());
3262 gInstance
= instance
;
3264 return WrapMovingNotNullUnchecked(std::move(instance
));
3267 void QuotaManager::GetOrCreate(nsIRunnable
* aCallback
) {
3268 AssertIsOnBackgroundThread();
3270 Unused
<< GetOrCreate();
3272 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback
));
3276 QuotaManager
* QuotaManager::Get() {
3277 // Does not return an owning reference.
3282 QuotaManager
& QuotaManager::GetRef() {
3283 MOZ_ASSERT(gInstance
);
3289 bool QuotaManager::IsShuttingDown() { return gShutdown
; }
3292 void QuotaManager::ShutdownInstance() {
3293 AssertIsOnBackgroundThread();
3296 gInstance
->Shutdown();
3298 gInstance
= nullptr;
3301 RefPtr
<Runnable
> runnable
=
3302 NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
3303 []() { Observer::ShutdownCompleted(); });
3304 MOZ_ASSERT(runnable
);
3306 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable
.forget()));
3310 bool QuotaManager::IsOSMetadata(const nsAString
& aFileName
) {
3311 return aFileName
.EqualsLiteral(DSSTORE_FILE_NAME
) ||
3312 aFileName
.EqualsLiteral(DESKTOP_FILE_NAME
) ||
3313 aFileName
.LowerCaseEqualsLiteral(DESKTOP_INI_FILE_NAME
) ||
3314 aFileName
.LowerCaseEqualsLiteral(THUMBS_DB_FILE_NAME
);
3318 bool QuotaManager::IsDotFile(const nsAString
& aFileName
) {
3319 return aFileName
.First() == char16_t('.');
3322 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl
& aLock
) {
3323 AssertIsOnOwningThread();
3325 mDirectoryLocks
.AppendElement(WrapNotNullUnchecked(&aLock
));
3327 if (aLock
.ShouldUpdateLockIdTable()) {
3328 MutexAutoLock
lock(mQuotaMutex
);
3330 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable
.Contains(aLock
.Id()));
3331 mDirectoryLockIdTable
.InsertOrUpdate(aLock
.Id(),
3332 WrapNotNullUnchecked(&aLock
));
3335 if (aLock
.ShouldUpdateLockTable()) {
3336 DirectoryLockTable
& directoryLockTable
=
3337 GetDirectoryLockTable(aLock
.GetPersistenceType());
3339 // XXX It seems that the contents of the array are never actually used, we
3340 // just use that like an inefficient use counter. Can't we just change
3341 // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
3343 .LookupOrInsertWith(
3346 if (!IsShuttingDown()) {
3347 UpdateOriginAccessTime(aLock
.GetPersistenceType(),
3348 aLock
.OriginMetadata());
3350 return MakeUnique
<nsTArray
<NotNull
<DirectoryLockImpl
*>>>();
3352 ->AppendElement(WrapNotNullUnchecked(&aLock
));
3355 aLock
.SetRegistered(true);
3358 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl
& aLock
) {
3359 AssertIsOnOwningThread();
3361 MOZ_ALWAYS_TRUE(mDirectoryLocks
.RemoveElement(&aLock
));
3363 if (aLock
.ShouldUpdateLockIdTable()) {
3364 MutexAutoLock
lock(mQuotaMutex
);
3366 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable
.Contains(aLock
.Id()));
3367 mDirectoryLockIdTable
.Remove(aLock
.Id());
3370 if (aLock
.ShouldUpdateLockTable()) {
3371 DirectoryLockTable
& directoryLockTable
=
3372 GetDirectoryLockTable(aLock
.GetPersistenceType());
3374 nsTArray
<NotNull
<DirectoryLockImpl
*>>* array
;
3375 MOZ_ALWAYS_TRUE(directoryLockTable
.Get(aLock
.Origin(), &array
));
3377 MOZ_ALWAYS_TRUE(array
->RemoveElement(&aLock
));
3378 if (array
->IsEmpty()) {
3379 directoryLockTable
.Remove(aLock
.Origin());
3381 if (!IsShuttingDown()) {
3382 UpdateOriginAccessTime(aLock
.GetPersistenceType(),
3383 aLock
.OriginMetadata());
3388 aLock
.SetRegistered(false);
3391 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl
& aLock
) {
3392 AssertIsOnOwningThread();
3394 mPendingDirectoryLocks
.AppendElement(&aLock
);
3397 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl
& aLock
) {
3398 AssertIsOnOwningThread();
3400 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks
.RemoveElement(&aLock
));
3403 uint64_t QuotaManager::CollectOriginsForEviction(
3404 uint64_t aMinSizeToBeFreed
, nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
) {
3405 AssertIsOnOwningThread();
3406 MOZ_ASSERT(aLocks
.IsEmpty());
3408 // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
3409 // or maybe a generalization if that.
3411 struct MOZ_STACK_CLASS Helper final
{
3412 static void GetInactiveOriginInfos(
3413 const nsTArray
<NotNull
<RefPtr
<OriginInfo
>>>& aOriginInfos
,
3414 const nsTArray
<NotNull
<const DirectoryLockImpl
*>>& aLocks
,
3415 OriginInfosFlatTraversable
& aInactiveOriginInfos
) {
3416 for (const auto& originInfo
: aOriginInfos
) {
3417 MOZ_ASSERT(originInfo
->mGroupInfo
->mPersistenceType
!=
3418 PERSISTENCE_TYPE_PERSISTENT
);
3420 if (originInfo
->LockedPersisted()) {
3424 const auto originScope
= OriginScope::FromOrigin(originInfo
->mOrigin
);
3427 std::any_of(aLocks
.begin(), aLocks
.end(),
3428 [&originScope
](const DirectoryLockImpl
* const lock
) {
3429 return originScope
.Matches(lock
->GetOriginScope());
3433 MOZ_ASSERT(!originInfo
->mQuotaObjects
.Count(),
3434 "Inactive origin shouldn't have open files!");
3435 aInactiveOriginInfos
.InsertElementSorted(
3436 originInfo
, OriginInfoAccessTimeComparator());
3442 // Split locks into separate arrays and filter out locks for persistent
3443 // storage, they can't block us.
3444 const auto [temporaryStorageLocks
, defaultStorageLocks
] = [this] {
3445 nsTArray
<NotNull
<const DirectoryLockImpl
*>> temporaryStorageLocks
;
3446 nsTArray
<NotNull
<const DirectoryLockImpl
*>> defaultStorageLocks
;
3447 for (NotNull
<const DirectoryLockImpl
*> const lock
: mDirectoryLocks
) {
3448 const Nullable
<PersistenceType
>& persistenceType
=
3449 lock
->NullablePersistenceType();
3451 if (persistenceType
.IsNull()) {
3452 temporaryStorageLocks
.AppendElement(lock
);
3453 defaultStorageLocks
.AppendElement(lock
);
3454 } else if (persistenceType
.Value() == PERSISTENCE_TYPE_TEMPORARY
) {
3455 temporaryStorageLocks
.AppendElement(lock
);
3456 } else if (persistenceType
.Value() == PERSISTENCE_TYPE_DEFAULT
) {
3457 defaultStorageLocks
.AppendElement(lock
);
3459 MOZ_ASSERT(persistenceType
.Value() == PERSISTENCE_TYPE_PERSISTENT
);
3461 // Do nothing here, persistent origins don't need to be collected ever.
3465 return std::pair(std::move(temporaryStorageLocks
),
3466 std::move(defaultStorageLocks
));
3469 // Enumerate and process inactive origins. This must be protected by the
3471 MutexAutoLock
lock(mQuotaMutex
);
3473 const auto [inactiveOrigins
, sizeToBeFreed
] =
3474 [this, &temporaryStorageLocks
= temporaryStorageLocks
,
3475 &defaultStorageLocks
= defaultStorageLocks
, aMinSizeToBeFreed
] {
3476 nsTArray
<NotNull
<RefPtr
<const OriginInfo
>>> inactiveOrigins
;
3477 for (const auto& entry
: mGroupInfoPairs
) {
3478 const auto& pair
= entry
.GetData();
3480 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
3483 RefPtr
<GroupInfo
> groupInfo
=
3484 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
);
3486 Helper::GetInactiveOriginInfos(groupInfo
->mOriginInfos
,
3487 temporaryStorageLocks
,
3491 groupInfo
= pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
);
3493 Helper::GetInactiveOriginInfos(
3494 groupInfo
->mOriginInfos
, defaultStorageLocks
, inactiveOrigins
);
3499 // Make sure the array is sorted correctly.
3500 const bool inactiveOriginsSorted
=
3501 std::is_sorted(inactiveOrigins
.cbegin(), inactiveOrigins
.cend(),
3502 [](const auto& lhs
, const auto& rhs
) {
3503 return lhs
->mAccessTime
< rhs
->mAccessTime
;
3505 MOZ_ASSERT(inactiveOriginsSorted
);
3508 // Create a list of inactive and the least recently used origins
3509 // whose aggregate size is greater or equals the minimal size to be
3511 uint64_t sizeToBeFreed
= 0;
3512 for (uint32_t count
= inactiveOrigins
.Length(), index
= 0;
3513 index
< count
; index
++) {
3514 if (sizeToBeFreed
>= aMinSizeToBeFreed
) {
3515 inactiveOrigins
.TruncateLength(index
);
3519 sizeToBeFreed
+= inactiveOrigins
[index
]->LockedUsage();
3522 return std::pair(std::move(inactiveOrigins
), sizeToBeFreed
);
3525 if (sizeToBeFreed
>= aMinSizeToBeFreed
) {
3526 // Success, add directory locks for these origins, so any other
3527 // operations for them will be delayed (until origin eviction is finalized).
3529 for (const auto& originInfo
: inactiveOrigins
) {
3530 auto lock
= DirectoryLockImpl::CreateForEviction(
3531 WrapNotNullUnchecked(this), originInfo
->mGroupInfo
->mPersistenceType
,
3532 originInfo
->FlattenToOriginMetadata());
3534 lock
->AcquireImmediately();
3536 aLocks
.AppendElement(lock
.forget());
3539 return sizeToBeFreed
;
3545 template <typename P
>
3546 void QuotaManager::CollectPendingOriginsForListing(P aPredicate
) {
3547 MutexAutoLock
lock(mQuotaMutex
);
3549 for (const auto& entry
: mGroupInfoPairs
) {
3550 const auto& pair
= entry
.GetData();
3552 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
3555 RefPtr
<GroupInfo
> groupInfo
=
3556 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
);
3558 for (const auto& originInfo
: groupInfo
->mOriginInfos
) {
3559 if (!originInfo
->mDirectoryExists
) {
3560 aPredicate(originInfo
);
3567 nsresult
QuotaManager::Init() {
3568 AssertIsOnOwningThread();
3571 CacheUseDOSDevicePathSyntaxPrefValue();
3574 QM_TRY_INSPECT(const auto& baseDir
, QM_NewLocalFile(mBasePath
));
3577 do_Init(mIndexedDBPath
),
3578 GetPathForStorage(*baseDir
, nsLiteralString(INDEXEDDB_DIRECTORY_NAME
)));
3580 QM_TRY(baseDir
->Append(mStorageName
));
3582 QM_TRY_UNWRAP(do_Init(mStoragePath
),
3583 MOZ_TO_RESULT_INVOKE_TYPED(nsString
, baseDir
, GetPath
));
3586 do_Init(mPermanentStoragePath
),
3587 GetPathForStorage(*baseDir
, nsLiteralString(PERMANENT_DIRECTORY_NAME
)));
3590 do_Init(mTemporaryStoragePath
),
3591 GetPathForStorage(*baseDir
, nsLiteralString(TEMPORARY_DIRECTORY_NAME
)));
3594 do_Init(mDefaultStoragePath
),
3595 GetPathForStorage(*baseDir
, nsLiteralString(DEFAULT_DIRECTORY_NAME
)));
3597 QM_TRY_UNWRAP(do_Init(mIOThread
),
3598 ToResultInvoke
<nsCOMPtr
<nsIThread
>>(
3599 MOZ_SELECT_OVERLOAD(NS_NewNamedThread
), "QuotaManager IO"));
3601 // Make a timer here to avoid potential failures later. We don't actually
3602 // initialize the timer until shutdown.
3603 nsCOMPtr shutdownTimer
= NS_NewTimer();
3604 QM_TRY(OkIf(shutdownTimer
), Err(NS_ERROR_FAILURE
));
3606 mShutdownTimer
.init(WrapNotNullUnchecked(std::move(shutdownTimer
)));
3608 static_assert(Client::IDB
== 0 && Client::DOMCACHE
== 1 && Client::SDB
== 2 &&
3609 Client::LS
== 3 && Client::TYPE_MAX
== 4,
3610 "Fix the registration!");
3612 // Register clients.
3613 auto clients
= decltype(mClients
)::ValueType
{};
3614 clients
.AppendElement(indexedDB::CreateQuotaClient());
3615 clients
.AppendElement(cache::CreateQuotaClient());
3616 clients
.AppendElement(simpledb::CreateQuotaClient());
3617 if (NextGenLocalStorageEnabled()) {
3618 clients
.AppendElement(localstorage::CreateQuotaClient());
3620 clients
.SetLength(Client::TypeMax());
3623 mClients
.init(std::move(clients
));
3625 MOZ_ASSERT(mClients
->Capacity() == Client::TYPE_MAX
,
3626 "Should be using an auto array with correct capacity!");
3628 mAllClientTypes
.init(ClientTypesArray
{Client::Type::IDB
,
3629 Client::Type::DOMCACHE
,
3630 Client::Type::SDB
, Client::Type::LS
});
3631 mAllClientTypesExceptLS
.init(ClientTypesArray
{
3632 Client::Type::IDB
, Client::Type::DOMCACHE
, Client::Type::SDB
});
3637 void QuotaManager::MaybeRecordShutdownStep(const Client::Type aClientType
,
3638 const nsACString
& aStepDescription
) {
3639 // Callable on any thread.
3641 MaybeRecordShutdownStep(Some(aClientType
), aStepDescription
);
3644 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
3645 const nsACString
& aStepDescription
) {
3646 // Callable on any thread.
3648 MaybeRecordShutdownStep(Nothing
{}, aStepDescription
);
3651 void QuotaManager::MaybeRecordShutdownStep(
3652 const Maybe
<Client::Type
> aClientType
, const nsACString
& aStepDescription
) {
3653 if (!mShutdownStarted
) {
3654 // We are not shutting down yet, we intentionally ignore this here to avoid
3655 // that every caller has to make a distinction for shutdown vs. non-shutdown
3660 const TimeDuration elapsedSinceShutdownStart
=
3661 TimeStamp::NowLoRes() - *mShutdownStartedAt
;
3663 const auto stepString
=
3664 nsPrintfCString("%fs: %s", elapsedSinceShutdownStart
.ToSeconds(),
3665 nsPromiseFlatCString(aStepDescription
).get());
3668 AssertIsOnBackgroundThread();
3670 mShutdownSteps
[*aClientType
].Append(stepString
+ "\n"_ns
);
3672 // Callable on any thread.
3673 MutexAutoLock
lock(mQuotaMutex
);
3675 mQuotaManagerShutdownSteps
.Append(stepString
+ "\n"_ns
);
3679 // XXX Probably this isn't the mechanism that should be used here.
3683 nsAutoCString(aClientType
? Client::TypeToText(*aClientType
)
3684 : "quota manager"_ns
+ " shutdown step"_ns
)
3686 stepString
.get(), __FILE__
, __LINE__
);
3690 void QuotaManager::Shutdown() {
3691 AssertIsOnOwningThread();
3692 MOZ_ASSERT(!mShutdownStarted
);
3694 // Setting this flag prevents the service from being recreated and prevents
3695 // further storagess from being created.
3696 if (gShutdown
.exchange(true)) {
3697 NS_ERROR("Shutdown more than once?!");
3700 StopIdleMaintenance();
3702 mShutdownStartedAt
.init(TimeStamp::NowLoRes());
3703 mShutdownStarted
= true;
3705 const auto& allClientTypes
= AllClientTypes();
3707 bool needsToWait
= false;
3708 for (Client::Type type
: allClientTypes
) {
3709 needsToWait
|= (*mClients
)[type
]->InitiateShutdownWorkThreads();
3711 needsToWait
|= static_cast<bool>(gNormalOriginOps
);
3713 // If any clients cannot shutdown immediately, spin the event loop while we
3714 // wait on all the threads to close. Our timer may fire during that loop.
3716 MOZ_ALWAYS_SUCCEEDS(
3718 ->InitWithNamedFuncCallback(
3719 [](nsITimer
* aTimer
, void* aClosure
) {
3720 auto* const quotaManager
=
3721 static_cast<QuotaManager
*>(aClosure
);
3723 for (Client::Type type
: quotaManager
->AllClientTypes()) {
3724 // XXX This is a workaround to unblock shutdown, which ought
3725 // to be removed by Bug 1682326.
3726 if (type
== Client::IDB
) {
3727 (*quotaManager
->mClients
)[type
]->AbortAllOperations();
3730 (*quotaManager
->mClients
)[type
]->ForceKillActors();
3733 MOZ_ALWAYS_SUCCEEDS(aTimer
->InitWithNamedFuncCallback(
3734 [](nsITimer
* aTimer
, void* aClosure
) {
3735 auto* const quotaManager
=
3736 static_cast<QuotaManager
*>(aClosure
);
3738 nsCString annotation
;
3741 for (Client::Type type
:
3742 quotaManager
->AllClientTypes()) {
3744 *(*quotaManager
->mClients
)[type
];
3746 if (!quotaClient
.IsShutdownCompleted()) {
3747 annotation
.AppendPrintf(
3748 "%s: %s\nIntermediate steps:\n%s\n\n",
3749 Client::TypeToText(type
).get(),
3750 quotaClient
.GetShutdownStatus().get(),
3751 quotaManager
->mShutdownSteps
[type
].get());
3755 if (gNormalOriginOps
) {
3756 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
3758 annotation
.AppendPrintf(
3759 "QM: %zu normal origin ops "
3760 "pending\nIntermediate "
3762 gNormalOriginOps
->Length(),
3763 quotaManager
->mQuotaManagerShutdownSteps
.get());
3767 // We expect that at least one quota client didn't
3768 // complete its shutdown.
3769 MOZ_DIAGNOSTIC_ASSERT(!annotation
.IsEmpty());
3771 CrashReporter::AnnotateCrashReport(
3772 CrashReporter::Annotation::
3773 QuotaManagerShutdownTimeout
,
3776 MOZ_CRASH("Quota manager shutdown timed out");
3778 aClosure
, SHUTDOWN_FORCE_CRASH_TIMEOUT_MS
,
3779 nsITimer::TYPE_ONE_SHOT
,
3780 "quota::QuotaManager::ForceCrashTimer"));
3782 this, SHUTDOWN_FORCE_KILL_TIMEOUT_MS
, nsITimer::TYPE_ONE_SHOT
,
3783 "quota::QuotaManager::ForceKillTimer"));
3785 MOZ_ALWAYS_TRUE(SpinEventLoopUntil([this, &allClientTypes
] {
3786 return !gNormalOriginOps
&&
3787 std::all_of(allClientTypes
.cbegin(), allClientTypes
.cend(),
3788 [&self
= *this](const auto type
) {
3789 return (*self
.mClients
)[type
]->IsShutdownCompleted();
3794 for (Client::Type type
: allClientTypes
) {
3795 (*mClients
)[type
]->FinalizeShutdownWorkThreads();
3798 // Cancel the timer regardless of whether it actually fired.
3799 QM_WARNONLY_TRY((*mShutdownTimer
)->Cancel());
3801 // NB: It's very important that runnable is destroyed on this thread
3802 // (i.e. after we join the IO thread) because we can't release the
3803 // QuotaManager on the IO thread. This should probably use
3804 // NewNonOwningRunnableMethod ...
3805 RefPtr
<Runnable
> runnable
=
3806 NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
3807 &QuotaManager::ShutdownStorage
);
3808 MOZ_ASSERT(runnable
);
3810 // Give clients a chance to cleanup IO thread only objects.
3811 QM_WARNONLY_TRY((*mIOThread
)->Dispatch(runnable
, NS_DISPATCH_NORMAL
));
3813 // Make sure to join with our IO thread.
3814 QM_WARNONLY_TRY((*mIOThread
)->Shutdown());
3816 for (RefPtr
<DirectoryLockImpl
>& lock
: mPendingDirectoryLocks
) {
3821 void QuotaManager::InitQuotaForOrigin(
3822 const FullOriginMetadata
& aFullOriginMetadata
,
3823 const ClientUsageArray
& aClientUsages
, uint64_t aUsageBytes
) {
3824 AssertIsOnIOThread();
3825 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata
.mPersistenceType
));
3827 MutexAutoLock
lock(mQuotaMutex
);
3829 RefPtr
<GroupInfo
> groupInfo
= LockedGetOrCreateGroupInfo(
3830 aFullOriginMetadata
.mPersistenceType
, aFullOriginMetadata
.mSuffix
,
3831 aFullOriginMetadata
.mGroup
);
3833 groupInfo
->LockedAddOriginInfo(MakeNotNull
<RefPtr
<OriginInfo
>>(
3834 groupInfo
, aFullOriginMetadata
.mOrigin
, aClientUsages
, aUsageBytes
,
3835 aFullOriginMetadata
.mLastAccessTime
, aFullOriginMetadata
.mPersisted
,
3836 /* aDirectoryExists */ true));
3839 void QuotaManager::EnsureQuotaForOrigin(const OriginMetadata
& aOriginMetadata
) {
3840 AssertIsOnIOThread();
3841 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata
.mPersistenceType
));
3843 MutexAutoLock
lock(mQuotaMutex
);
3845 RefPtr
<GroupInfo
> groupInfo
= LockedGetOrCreateGroupInfo(
3846 aOriginMetadata
.mPersistenceType
, aOriginMetadata
.mSuffix
,
3847 aOriginMetadata
.mGroup
);
3849 RefPtr
<OriginInfo
> originInfo
=
3850 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
3852 groupInfo
->LockedAddOriginInfo(MakeNotNull
<RefPtr
<OriginInfo
>>(
3853 groupInfo
, aOriginMetadata
.mOrigin
, ClientUsageArray(),
3854 /* aUsageBytes */ 0,
3855 /* aAccessTime */ PR_Now(), /* aPersisted */ false,
3856 /* aDirectoryExists */ false));
3860 int64_t QuotaManager::NoteOriginDirectoryCreated(
3861 const OriginMetadata
& aOriginMetadata
, bool aPersisted
) {
3862 AssertIsOnIOThread();
3863 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata
.mPersistenceType
));
3867 MutexAutoLock
lock(mQuotaMutex
);
3869 RefPtr
<GroupInfo
> groupInfo
= LockedGetOrCreateGroupInfo(
3870 aOriginMetadata
.mPersistenceType
, aOriginMetadata
.mSuffix
,
3871 aOriginMetadata
.mGroup
);
3873 RefPtr
<OriginInfo
> originInfo
=
3874 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
3876 timestamp
= originInfo
->LockedAccessTime();
3877 originInfo
->mPersisted
= aPersisted
;
3878 originInfo
->mDirectoryExists
= true;
3880 timestamp
= PR_Now();
3881 groupInfo
->LockedAddOriginInfo(MakeNotNull
<RefPtr
<OriginInfo
>>(
3882 groupInfo
, aOriginMetadata
.mOrigin
, ClientUsageArray(),
3883 /* aUsageBytes */ 0,
3884 /* aAccessTime */ timestamp
, aPersisted
, /* aDirectoryExists */ true));
3890 void QuotaManager::DecreaseUsageForClient(const ClientMetadata
& aClientMetadata
,
3892 MOZ_ASSERT(!NS_IsMainThread());
3893 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata
.mPersistenceType
));
3895 MutexAutoLock
lock(mQuotaMutex
);
3897 GroupInfoPair
* pair
;
3898 if (!mGroupInfoPairs
.Get(aClientMetadata
.mGroup
, &pair
)) {
3902 RefPtr
<GroupInfo
> groupInfo
=
3903 pair
->LockedGetGroupInfo(aClientMetadata
.mPersistenceType
);
3908 RefPtr
<OriginInfo
> originInfo
=
3909 groupInfo
->LockedGetOriginInfo(aClientMetadata
.mOrigin
);
3911 originInfo
->LockedDecreaseUsage(aClientMetadata
.mClientType
, aSize
);
3915 void QuotaManager::ResetUsageForClient(const ClientMetadata
& aClientMetadata
) {
3916 MOZ_ASSERT(!NS_IsMainThread());
3917 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata
.mPersistenceType
));
3919 MutexAutoLock
lock(mQuotaMutex
);
3921 GroupInfoPair
* pair
;
3922 if (!mGroupInfoPairs
.Get(aClientMetadata
.mGroup
, &pair
)) {
3926 RefPtr
<GroupInfo
> groupInfo
=
3927 pair
->LockedGetGroupInfo(aClientMetadata
.mPersistenceType
);
3932 RefPtr
<OriginInfo
> originInfo
=
3933 groupInfo
->LockedGetOriginInfo(aClientMetadata
.mOrigin
);
3935 originInfo
->LockedResetUsageForClient(aClientMetadata
.mClientType
);
3939 UsageInfo
QuotaManager::GetUsageForClient(PersistenceType aPersistenceType
,
3940 const OriginMetadata
& aOriginMetadata
,
3941 Client::Type aClientType
) {
3942 MOZ_ASSERT(!NS_IsMainThread());
3943 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
3945 MutexAutoLock
lock(mQuotaMutex
);
3947 GroupInfoPair
* pair
;
3948 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
3952 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
3957 RefPtr
<OriginInfo
> originInfo
=
3958 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
3963 return originInfo
->LockedGetUsageForClient(aClientType
);
3966 void QuotaManager::UpdateOriginAccessTime(
3967 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
) {
3968 AssertIsOnOwningThread();
3969 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
3970 MOZ_ASSERT(!IsShuttingDown());
3972 MutexAutoLock
lock(mQuotaMutex
);
3974 GroupInfoPair
* pair
;
3975 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
3979 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
3984 RefPtr
<OriginInfo
> originInfo
=
3985 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
3987 int64_t timestamp
= PR_Now();
3988 originInfo
->LockedUpdateAccessTime(timestamp
);
3990 MutexAutoUnlock
autoUnlock(mQuotaMutex
);
3992 auto op
= MakeRefPtr
<SaveOriginAccessTimeOp
>(
3993 aPersistenceType
, aOriginMetadata
.mOrigin
, timestamp
);
3995 RegisterNormalOriginOp(*op
);
3997 op
->RunImmediately();
4001 void QuotaManager::RemoveQuota() {
4002 AssertIsOnIOThread();
4004 MutexAutoLock
lock(mQuotaMutex
);
4006 for (const auto& entry
: mGroupInfoPairs
) {
4007 const auto& pair
= entry
.GetData();
4009 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
4012 RefPtr
<GroupInfo
> groupInfo
=
4013 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
);
4015 groupInfo
->LockedRemoveOriginInfos();
4018 groupInfo
= pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
);
4020 groupInfo
->LockedRemoveOriginInfos();
4024 mGroupInfoPairs
.Clear();
4026 MOZ_ASSERT(mTemporaryStorageUsage
== 0, "Should be zero!");
4029 nsresult
QuotaManager::LoadQuota() {
4030 AssertIsOnIOThread();
4031 MOZ_ASSERT(mStorageConnection
);
4032 MOZ_ASSERT(!mTemporaryStorageInitialized
);
4034 auto recordQuotaInfoLoadTimeHelper
=
4035 MakeRefPtr
<RecordQuotaInfoLoadTimeHelper
>();
4036 recordQuotaInfoLoadTimeHelper
->Start();
4038 auto LoadQuotaFromCache
= [&]() -> nsresult
{
4041 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<mozIStorageStatement
>,
4042 mStorageConnection
, CreateStatement
,
4043 "SELECT repository_id, suffix, group_, "
4044 "origin, client_usages, usage, "
4045 "last_access_time, accessed, persisted "
4048 auto autoRemoveQuota
= MakeScopeExit([&] { RemoveQuota(); });
4050 QM_TRY(quota::CollectWhileHasResult(
4051 *stmt
, [this](auto& stmt
) -> Result
<Ok
, nsresult
> {
4052 QM_TRY_INSPECT(const int32_t& repositoryId
,
4053 MOZ_TO_RESULT_INVOKE(stmt
, GetInt32
, 0));
4055 const auto maybePersistenceType
=
4056 PersistenceTypeFromInt32(repositoryId
, fallible
);
4057 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
4059 FullOriginMetadata fullOriginMetadata
;
4061 fullOriginMetadata
.mPersistenceType
= maybePersistenceType
.value();
4064 fullOriginMetadata
.mSuffix
,
4065 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, stmt
, GetUTF8String
, 1));
4068 fullOriginMetadata
.mGroup
,
4069 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, stmt
, GetUTF8String
, 2));
4072 fullOriginMetadata
.mOrigin
,
4073 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, stmt
, GetUTF8String
, 3));
4075 QM_TRY_INSPECT(const bool& updated
,
4076 MaybeUpdateGroupForOrigin(fullOriginMetadata
));
4080 // We don't need to update the .metadata-v2 file on disk here,
4081 // EnsureTemporaryOriginIsInitialized is responsible for doing that.
4082 // We just need to use correct group before initializing quota for the
4083 // given origin. (Note that calling LoadFullOriginMetadataWithRestore
4084 // below might update the group in the metadata file, but only as a
4085 // side-effect. The actual place we ensure consistency is in
4086 // EnsureTemporaryOriginIsInitialized.)
4089 const auto& clientUsagesText
,
4090 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, stmt
, GetUTF8String
, 4));
4092 ClientUsageArray clientUsages
;
4093 QM_TRY(clientUsages
.Deserialize(clientUsagesText
));
4095 QM_TRY_INSPECT(const int64_t& usage
,
4096 MOZ_TO_RESULT_INVOKE(stmt
, GetInt64
, 5));
4097 QM_TRY_UNWRAP(fullOriginMetadata
.mLastAccessTime
,
4098 MOZ_TO_RESULT_INVOKE(stmt
, GetInt64
, 6));
4099 QM_TRY_INSPECT(const int64_t& accessed
,
4100 MOZ_TO_RESULT_INVOKE(stmt
, GetInt32
, 7));
4101 QM_TRY_UNWRAP(fullOriginMetadata
.mPersisted
,
4102 MOZ_TO_RESULT_INVOKE(stmt
, GetInt32
, 8));
4106 const auto& directory
,
4107 GetDirectoryForOrigin(fullOriginMetadata
.mPersistenceType
,
4108 fullOriginMetadata
.mOrigin
));
4110 QM_TRY_INSPECT(const bool& exists
,
4111 MOZ_TO_RESULT_INVOKE(directory
, Exists
));
4113 QM_TRY(OkIf(exists
), Err(NS_ERROR_FAILURE
));
4115 QM_TRY_INSPECT(const bool& isDirectory
,
4116 MOZ_TO_RESULT_INVOKE(directory
, IsDirectory
));
4118 QM_TRY(OkIf(isDirectory
), Err(NS_ERROR_FAILURE
));
4120 // Calling LoadFullOriginMetadataWithRestore might update the group
4121 // in the metadata file, but only as a side-effect. The actual place
4122 // we ensure consistency is in EnsureTemporaryOriginIsInitialized.
4124 QM_TRY_INSPECT(const auto& metadata
,
4125 LoadFullOriginMetadataWithRestore(directory
));
4127 QM_TRY(OkIf(fullOriginMetadata
.mLastAccessTime
==
4128 metadata
.mLastAccessTime
),
4129 Err(NS_ERROR_FAILURE
));
4131 QM_TRY(OkIf(fullOriginMetadata
.mPersisted
== metadata
.mPersisted
),
4132 Err(NS_ERROR_FAILURE
));
4134 QM_TRY(OkIf(fullOriginMetadata
.mPersistenceType
==
4135 metadata
.mPersistenceType
),
4136 Err(NS_ERROR_FAILURE
));
4138 QM_TRY(OkIf(fullOriginMetadata
.mSuffix
== metadata
.mSuffix
),
4139 Err(NS_ERROR_FAILURE
));
4141 QM_TRY(OkIf(fullOriginMetadata
.mGroup
== metadata
.mGroup
),
4142 Err(NS_ERROR_FAILURE
));
4144 QM_TRY(OkIf(fullOriginMetadata
.mOrigin
== metadata
.mOrigin
),
4145 Err(NS_ERROR_FAILURE
));
4147 QM_TRY(InitializeOrigin(fullOriginMetadata
.mPersistenceType
,
4149 fullOriginMetadata
.mLastAccessTime
,
4150 fullOriginMetadata
.mPersisted
, directory
));
4152 InitQuotaForOrigin(fullOriginMetadata
, clientUsages
, usage
);
4158 autoRemoveQuota
.release();
4164 const bool& loadQuotaFromCache
, ([this]() -> Result
<bool, nsresult
> {
4168 CreateAndExecuteSingleStepStatement
<
4169 SingleStepResult::ReturnNullIfNoResult
>(
4170 *mStorageConnection
, "SELECT valid, build_id FROM cache"_ns
));
4172 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FILE_CORRUPTED
));
4174 QM_TRY_INSPECT(const int32_t& valid
,
4175 MOZ_TO_RESULT_INVOKE(stmt
, GetInt32
, 0));
4178 QM_TRY_INSPECT(const auto& buildId
,
4179 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoCString
, stmt
,
4182 return buildId
== *gBuildId
;
4189 auto autoRemoveQuota
= MakeScopeExit([&] { RemoveQuota(); });
4191 if (!loadQuotaFromCache
||
4192 !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
4193 ![&LoadQuotaFromCache
] {
4194 QM_TRY(LoadQuotaFromCache(), false);
4197 // A keeper to defer the return only in Nightly, so that the telemetry data
4198 // for whole profile can be collected.
4199 #ifdef NIGHTLY_BUILD
4200 nsresult statusKeeper
= NS_OK
;
4203 const auto statusKeeperFunc
= [&](const nsresult rv
) {
4204 RECORD_IN_NIGHTLY(statusKeeper
, rv
);
4207 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
4208 if (NS_WARN_IF(IsShuttingDown())) {
4209 RETURN_STATUS_OR_RESULT(statusKeeper
, NS_ERROR_ABORT
);
4212 QM_TRY(([&]() -> Result
<Ok
, nsresult
> {
4213 QM_TRY(([this, type
] {
4214 const nsresult rv
= InitializeRepository(type
);
4215 mInitializationInfo
.RecordFirstInitializationAttempt(
4216 type
== PERSISTENCE_TYPE_DEFAULT
4217 ? Initialization::DefaultRepository
4218 : Initialization::TemporaryRepository
,
4222 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
4228 #ifdef NIGHTLY_BUILD
4229 if (NS_FAILED(statusKeeper
)) {
4230 return statusKeeper
;
4235 recordQuotaInfoLoadTimeHelper
->End();
4237 autoRemoveQuota
.release();
4242 void QuotaManager::UnloadQuota() {
4243 AssertIsOnIOThread();
4244 MOZ_ASSERT(mStorageConnection
);
4245 MOZ_ASSERT(mTemporaryStorageInitialized
);
4246 MOZ_ASSERT(mCacheUsable
);
4248 auto autoRemoveQuota
= MakeScopeExit([&] { RemoveQuota(); });
4250 mozStorageTransaction
transaction(
4251 mStorageConnection
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
4253 QM_TRY(transaction
.Start(), QM_VOID
);
4255 QM_TRY(mStorageConnection
->ExecuteSimpleSQL("DELETE FROM origin;"_ns
),
4258 nsCOMPtr
<mozIStorageStatement
> insertStmt
;
4261 MutexAutoLock
lock(mQuotaMutex
);
4263 for (auto iter
= mGroupInfoPairs
.Iter(); !iter
.Done(); iter
.Next()) {
4264 MOZ_ASSERT(!iter
.Key().IsEmpty());
4266 GroupInfoPair
* const pair
= iter
.UserData();
4269 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
4270 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(type
);
4275 for (const auto& originInfo
: groupInfo
->mOriginInfos
) {
4276 MOZ_ASSERT(!originInfo
->mQuotaObjects
.Count());
4278 if (!originInfo
->mDirectoryExists
) {
4283 MOZ_ALWAYS_SUCCEEDS(insertStmt
->Reset());
4287 MOZ_TO_RESULT_INVOKE_TYPED(
4288 nsCOMPtr
<mozIStorageStatement
>, mStorageConnection
,
4290 "INSERT INTO origin (repository_id, suffix, group_, "
4291 "origin, client_usages, usage, last_access_time, "
4292 "accessed, persisted) "
4293 "VALUES (:repository_id, :suffix, :group_, :origin, "
4294 ":client_usages, :usage, :last_access_time, :accessed, "
4299 QM_TRY(originInfo
->LockedBindToStatement(insertStmt
), QM_VOID
);
4301 QM_TRY(insertStmt
->Execute(), QM_VOID
);
4304 groupInfo
->LockedRemoveOriginInfos();
4313 MOZ_TO_RESULT_INVOKE_TYPED(
4314 nsCOMPtr
<mozIStorageStatement
>, mStorageConnection
, CreateStatement
,
4315 "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns
),
4318 QM_TRY(stmt
->BindInt32ByName("valid"_ns
, 1), QM_VOID
);
4319 QM_TRY(stmt
->BindUTF8StringByName("buildId"_ns
, *gBuildId
), QM_VOID
);
4320 QM_TRY(stmt
->Execute(), QM_VOID
);
4321 QM_TRY(transaction
.Commit(), QM_VOID
);
4324 already_AddRefed
<QuotaObject
> QuotaManager::GetQuotaObject(
4325 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
4326 Client::Type aClientType
, nsIFile
* aFile
, int64_t aFileSize
,
4327 int64_t* aFileSizeOut
/* = nullptr */) {
4328 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4334 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
4338 QM_TRY_INSPECT(const auto& path
,
4339 MOZ_TO_RESULT_INVOKE_TYPED(nsString
, aFile
, GetPath
), nullptr);
4344 const auto& directory
,
4345 GetDirectoryForOrigin(aPersistenceType
, aOriginMetadata
.mOrigin
),
4348 nsAutoString clientType
;
4349 QM_TRY(OkIf(Client::TypeToText(aClientType
, clientType
, fallible
)),
4352 QM_TRY(directory
->Append(clientType
), nullptr);
4354 QM_TRY_INSPECT(const auto& directoryPath
,
4355 MOZ_TO_RESULT_INVOKE_TYPED(nsString
, directory
, GetPath
),
4358 MOZ_ASSERT(StringBeginsWith(path
, directoryPath
));
4362 QM_TRY_INSPECT(const int64_t fileSize
,
4363 ([&aFile
, aFileSize
]() -> Result
<int64_t, nsresult
> {
4364 if (aFileSize
== -1) {
4365 QM_TRY_INSPECT(const bool& exists
,
4366 MOZ_TO_RESULT_INVOKE(aFile
, Exists
));
4369 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(aFile
, GetFileSize
));
4379 RefPtr
<QuotaObject
> result
;
4381 MutexAutoLock
lock(mQuotaMutex
);
4383 GroupInfoPair
* pair
;
4384 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
4388 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
4394 RefPtr
<OriginInfo
> originInfo
=
4395 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
4401 // We need this extra raw pointer because we can't assign to the smart
4402 // pointer directly since QuotaObject::AddRef would try to acquire the same
4404 const NotNull
<QuotaObject
*> quotaObject
=
4405 originInfo
->mQuotaObjects
.LookupOrInsertWith(path
, [&] {
4406 // Create a new QuotaObject. The hashtable is not responsible to
4407 // delete the QuotaObject.
4408 return WrapNotNullUnchecked(
4409 new QuotaObject(originInfo
, aClientType
, path
, fileSize
));
4412 // Addref the QuotaObject and move the ownership to the result. This must
4413 // happen before we unlock!
4414 result
= quotaObject
->LockedAddRef();
4418 *aFileSizeOut
= fileSize
;
4421 // The caller becomes the owner of the QuotaObject, that is, the caller is
4422 // is responsible to delete it when the last reference is removed.
4423 return result
.forget();
4426 already_AddRefed
<QuotaObject
> QuotaManager::GetQuotaObject(
4427 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
4428 Client::Type aClientType
, const nsAString
& aPath
, int64_t aFileSize
,
4429 int64_t* aFileSizeOut
/* = nullptr */) {
4430 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4436 QM_TRY_INSPECT(const auto& file
, QM_NewLocalFile(aPath
), nullptr);
4438 return GetQuotaObject(aPersistenceType
, aOriginMetadata
, aClientType
, file
,
4439 aFileSize
, aFileSizeOut
);
4442 already_AddRefed
<QuotaObject
> QuotaManager::GetQuotaObject(
4443 const int64_t aDirectoryLockId
, const nsAString
& aPath
) {
4444 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4446 Maybe
<MutexAutoLock
> lock
;
4448 // See the comment for mDirectoryLockIdTable in QuotaManager.h
4449 if (!IsOnBackgroundThread()) {
4450 lock
.emplace(mQuotaMutex
);
4453 if (auto maybeDirectoryLock
=
4454 mDirectoryLockIdTable
.MaybeGet(aDirectoryLockId
)) {
4455 const auto& directoryLock
= *maybeDirectoryLock
;
4456 MOZ_DIAGNOSTIC_ASSERT(directoryLock
->ShouldUpdateLockIdTable());
4458 const PersistenceType persistenceType
= directoryLock
->GetPersistenceType();
4459 const OriginMetadata
& originMetadata
= directoryLock
->OriginMetadata();
4460 const Client::Type clientType
= directoryLock
->ClientType();
4464 return GetQuotaObject(persistenceType
, originMetadata
, clientType
, aPath
);
4467 MOZ_CRASH("Getting quota object for an unregistered directory lock?");
4470 Nullable
<bool> QuotaManager::OriginPersisted(
4471 const OriginMetadata
& aOriginMetadata
) {
4472 AssertIsOnIOThread();
4474 MutexAutoLock
lock(mQuotaMutex
);
4476 RefPtr
<OriginInfo
> originInfo
=
4477 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT
, aOriginMetadata
);
4479 return Nullable
<bool>(originInfo
->LockedPersisted());
4482 return Nullable
<bool>();
4485 void QuotaManager::PersistOrigin(const OriginMetadata
& aOriginMetadata
) {
4486 AssertIsOnIOThread();
4488 MutexAutoLock
lock(mQuotaMutex
);
4490 RefPtr
<OriginInfo
> originInfo
=
4491 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT
, aOriginMetadata
);
4492 if (originInfo
&& !originInfo
->LockedPersisted()) {
4493 originInfo
->LockedPersist();
4497 void QuotaManager::AbortOperationsForLocks(
4498 const DirectoryLockIdTableArray
& aLockIds
) {
4499 for (Client::Type type
: AllClientTypes()) {
4500 if (aLockIds
[type
].Filled()) {
4501 (*mClients
)[type
]->AbortOperationsForLocks(aLockIds
[type
]);
4506 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId
) {
4507 AssertIsOnOwningThread();
4509 for (const RefPtr
<Client
>& client
: *mClients
) {
4510 client
->AbortOperationsForProcess(aContentParentId
);
4514 Result
<nsCOMPtr
<nsIFile
>, nsresult
> QuotaManager::GetDirectoryForOrigin(
4515 PersistenceType aPersistenceType
, const nsACString
& aASCIIOrigin
) const {
4516 QM_TRY_UNWRAP(auto directory
,
4517 QM_NewLocalFile(GetStoragePath(aPersistenceType
)));
4519 QM_TRY(directory
->Append(MakeSanitizedOriginString(aASCIIOrigin
)));
4524 nsresult
QuotaManager::RestoreDirectoryMetadata2(nsIFile
* aDirectory
) {
4525 AssertIsOnIOThread();
4526 MOZ_ASSERT(aDirectory
);
4527 MOZ_ASSERT(mStorageConnection
);
4529 RefPtr
<RestoreDirectoryMetadata2Helper
> helper
=
4530 new RestoreDirectoryMetadata2Helper(aDirectory
);
4532 QM_TRY(helper
->Init());
4534 QM_TRY(helper
->RestoreMetadata2File());
4539 Result
<FullOriginMetadata
, nsresult
> QuotaManager::LoadFullOriginMetadata(
4540 nsIFile
* aDirectory
, PersistenceType aPersistenceType
) {
4541 MOZ_ASSERT(!NS_IsMainThread());
4542 MOZ_ASSERT(aDirectory
);
4543 MOZ_ASSERT(mStorageConnection
);
4545 QM_TRY_INSPECT(const auto& binaryStream
,
4546 GetBinaryInputStream(*aDirectory
,
4547 nsLiteralString(METADATA_V2_FILE_NAME
)));
4549 FullOriginMetadata fullOriginMetadata
;
4551 QM_TRY_UNWRAP(fullOriginMetadata
.mLastAccessTime
,
4552 MOZ_TO_RESULT_INVOKE(binaryStream
, Read64
));
4554 QM_TRY_UNWRAP(fullOriginMetadata
.mPersisted
,
4555 MOZ_TO_RESULT_INVOKE(binaryStream
, ReadBoolean
));
4557 QM_TRY_INSPECT(const bool& reservedData1
,
4558 MOZ_TO_RESULT_INVOKE(binaryStream
, Read32
));
4559 Unused
<< reservedData1
;
4561 // XXX Use for the persistence type.
4562 QM_TRY_INSPECT(const bool& reservedData2
,
4563 MOZ_TO_RESULT_INVOKE(binaryStream
, Read32
));
4564 Unused
<< reservedData2
;
4566 fullOriginMetadata
.mPersistenceType
= aPersistenceType
;
4569 fullOriginMetadata
.mSuffix
,
4570 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, binaryStream
, ReadCString
));
4573 fullOriginMetadata
.mGroup
,
4574 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, binaryStream
, ReadCString
));
4577 fullOriginMetadata
.mOrigin
,
4578 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, binaryStream
, ReadCString
));
4580 // Currently unused (used to be isApp).
4581 QM_TRY_INSPECT(const bool& dummy
,
4582 MOZ_TO_RESULT_INVOKE(binaryStream
, ReadBoolean
));
4585 QM_TRY(binaryStream
->Close());
4587 QM_TRY_INSPECT(const bool& updated
,
4588 MaybeUpdateGroupForOrigin(fullOriginMetadata
));
4591 // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
4593 QM_TRY(CreateDirectoryMetadata2(
4594 *aDirectory
, fullOriginMetadata
.mLastAccessTime
,
4595 fullOriginMetadata
.mPersisted
, fullOriginMetadata
));
4598 return fullOriginMetadata
;
4601 Result
<FullOriginMetadata
, nsresult
>
4602 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile
* aDirectory
) {
4603 // XXX Once the persistence type is stored in the metadata file, this block
4604 // for getting the persistence type from the parent directory name can be
4606 nsCOMPtr
<nsIFile
> parentDir
;
4607 QM_TRY(aDirectory
->GetParent(getter_AddRefs(parentDir
)));
4609 const auto maybePersistenceType
=
4610 PersistenceTypeFromFile(*parentDir
, fallible
);
4611 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
4613 const auto& persistenceType
= maybePersistenceType
.value();
4615 QM_TRY_RETURN(QM_OR_ELSE_WARN(
4616 LoadFullOriginMetadata(aDirectory
, persistenceType
),
4617 ([&aDirectory
, &persistenceType
,
4618 this](const nsresult rv
) -> Result
<FullOriginMetadata
, nsresult
> {
4619 QM_TRY(RestoreDirectoryMetadata2(aDirectory
));
4621 QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory
, persistenceType
));
4625 nsresult
QuotaManager::InitializeRepository(PersistenceType aPersistenceType
) {
4626 MOZ_ASSERT(aPersistenceType
== PERSISTENCE_TYPE_TEMPORARY
||
4627 aPersistenceType
== PERSISTENCE_TYPE_DEFAULT
);
4629 QM_TRY_INSPECT(const auto& directory
,
4630 QM_NewLocalFile(GetStoragePath(aPersistenceType
)));
4632 QM_TRY_INSPECT(const bool& created
, EnsureDirectory(*directory
));
4636 // A keeper to defer the return only in Nightly, so that the telemetry data
4637 // for whole profile can be collected
4638 #ifdef NIGHTLY_BUILD
4639 nsresult statusKeeper
= NS_OK
;
4642 const auto statusKeeperFunc
= [&](const nsresult rv
) {
4643 RECORD_IN_NIGHTLY(statusKeeper
, rv
);
4646 struct RenameAndInitInfo
{
4647 nsCOMPtr
<nsIFile
> mOriginDirectory
;
4648 FullOriginMetadata mFullOriginMetadata
;
4652 nsTArray
<RenameAndInitInfo
> renameAndInitInfos
;
4654 QM_TRY(([&]() -> Result
<Ok
, nsresult
> {
4658 [&](nsCOMPtr
<nsIFile
>&& childDirectory
) -> Result
<Ok
, nsresult
> {
4659 if (NS_WARN_IF(IsShuttingDown())) {
4660 RETURN_STATUS_OR_RESULT(statusKeeper
, NS_ERROR_ABORT
);
4664 ([this, &childDirectory
, &renameAndInitInfos
,
4665 aPersistenceType
]() -> Result
<Ok
, nsresult
> {
4667 const auto& leafName
,
4668 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, childDirectory
,
4671 QM_TRY_INSPECT(const auto& dirEntryKind
,
4672 GetDirEntryKind(*childDirectory
));
4674 switch (dirEntryKind
) {
4675 case nsIFileKind::ExistsAsDirectory
: {
4678 LoadFullOriginMetadataWithRestore(childDirectory
));
4680 MOZ_ASSERT(metadata
.mPersistenceType
==
4683 // FIXME(tt): The check for origin name consistency can
4684 // be removed once we have an upgrade to traverse origin
4685 // directories and check through the directory metadata
4687 const auto originSanitized
=
4688 MakeSanitizedOriginCString(metadata
.mOrigin
);
4690 NS_ConvertUTF16toUTF8
utf8LeafName(leafName
);
4691 if (!originSanitized
.Equals(utf8LeafName
)) {
4693 "The name of the origin directory (%s) doesn't "
4694 "match the sanitized origin string (%s) in the "
4696 utf8LeafName
.get(), originSanitized
.get());
4698 // If it's the known case, we try to restore the
4699 // origin directory name if it's possible.
4700 if (originSanitized
.Equals(utf8LeafName
+ "."_ns
)) {
4701 const int64_t lastAccessTime
=
4702 metadata
.mLastAccessTime
;
4703 const bool persisted
= metadata
.mPersisted
;
4704 renameAndInitInfos
.AppendElement(RenameAndInitInfo
{
4705 std::move(childDirectory
), std::move(metadata
),
4706 lastAccessTime
, persisted
});
4710 // XXXtt: Try to restore the unknown cases base on the
4711 // content for their metadata files. Note that if the
4712 // restore fails, QM should maintain a list and ensure
4713 // they won't be accessed after initialization.
4716 QM_TRY(QM_OR_ELSE_WARN(
4717 ToResult(InitializeOrigin(
4718 aPersistenceType
, metadata
,
4719 metadata
.mLastAccessTime
, metadata
.mPersisted
,
4722 const nsresult rv
) -> Result
<Ok
, nsresult
> {
4723 if (IsDatabaseCorruptionError(rv
)) {
4724 // If the origin can't be initialized due to
4725 // corruption, this is a permanent
4726 // condition, and we need to remove all data
4727 // for the origin on disk.
4729 QM_TRY(childDirectory
->Remove(true));
4740 case nsIFileKind::ExistsAsFile
:
4741 if (IsOSMetadata(leafName
) || IsDotFile(leafName
)) {
4745 // Unknown files during initialization are now allowed.
4746 // Just warn if we find them.
4747 UNKNOWN_FILE_WARNING(leafName
);
4750 case nsIFileKind::DoesNotExist
:
4751 // Ignore files that got removed externally while
4758 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
4762 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
4767 for (const auto& info
: renameAndInitInfos
) {
4768 QM_TRY(([&]() -> Result
<Ok
, nsresult
> {
4769 QM_TRY(([&directory
, &info
, this,
4770 aPersistenceType
]() -> Result
<Ok
, nsresult
> {
4771 const auto originDirName
=
4772 MakeSanitizedOriginString(info
.mFullOriginMetadata
.mOrigin
);
4774 // Check if targetDirectory exist.
4775 QM_TRY_INSPECT(const auto& targetDirectory
,
4776 CloneFileAndAppend(*directory
, originDirName
));
4778 QM_TRY_INSPECT(const bool& exists
,
4779 MOZ_TO_RESULT_INVOKE(targetDirectory
, Exists
));
4782 QM_TRY(info
.mOriginDirectory
->Remove(true));
4787 QM_TRY(info
.mOriginDirectory
->RenameTo(nullptr, originDirName
));
4789 QM_TRY(InitializeOrigin(
4790 aPersistenceType
, info
.mFullOriginMetadata
, info
.mTimestamp
,
4791 info
.mPersisted
, targetDirectory
));
4795 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
4801 #ifdef NIGHTLY_BUILD
4802 if (NS_FAILED(statusKeeper
)) {
4803 return statusKeeper
;
4810 nsresult
QuotaManager::InitializeOrigin(PersistenceType aPersistenceType
,
4811 const OriginMetadata
& aOriginMetadata
,
4812 int64_t aAccessTime
, bool aPersisted
,
4813 nsIFile
* aDirectory
) {
4814 AssertIsOnIOThread();
4816 const bool trackQuota
= aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
;
4818 // We need to initialize directories of all clients if they exists and also
4819 // get the total usage to initialize the quota.
4821 ClientUsageArray clientUsages
;
4823 // A keeper to defer the return only in Nightly, so that the telemetry data
4824 // for whole profile can be collected
4825 #ifdef NIGHTLY_BUILD
4826 nsresult statusKeeper
= NS_OK
;
4829 QM_TRY(([&, statusKeeperFunc
= [&](const nsresult rv
) {
4830 RECORD_IN_NIGHTLY(statusKeeper
, rv
);
4831 }]() -> Result
<Ok
, nsresult
> {
4835 [&](const nsCOMPtr
<nsIFile
>& file
) -> Result
<Ok
, nsresult
> {
4836 if (NS_WARN_IF(IsShuttingDown())) {
4837 RETURN_STATUS_OR_RESULT(statusKeeper
, NS_ERROR_ABORT
);
4841 ([this, &file
, trackQuota
, aPersistenceType
, &aOriginMetadata
,
4842 &clientUsages
]() -> Result
<Ok
, nsresult
> {
4843 QM_TRY_INSPECT(const auto& leafName
,
4844 MOZ_TO_RESULT_INVOKE_TYPED(
4845 nsAutoString
, file
, GetLeafName
));
4847 QM_TRY_INSPECT(const auto& dirEntryKind
,
4848 GetDirEntryKind(*file
));
4850 switch (dirEntryKind
) {
4851 case nsIFileKind::ExistsAsDirectory
: {
4852 Client::Type clientType
;
4853 const bool ok
= Client::TypeFromText(
4854 leafName
, clientType
, fallible
);
4856 // Unknown directories during initialization are now
4857 // allowed. Just warn if we find them.
4858 UNKNOWN_FILE_WARNING(leafName
);
4864 const auto& usageInfo
,
4865 (*mClients
)[clientType
]->InitOrigin(
4866 aPersistenceType
, aOriginMetadata
,
4867 /* aCanceled */ Atomic
<bool>(false)));
4869 MOZ_ASSERT(!clientUsages
[clientType
]);
4871 if (usageInfo
.TotalUsage()) {
4872 // XXX(Bug 1683863) Until we identify the root cause
4873 // of seemingly converted-from-negative usage
4874 // values, we will just treat them as unset here,
4875 // but log a warning to the browser console.
4876 if (static_cast<int64_t>(*usageInfo
.TotalUsage()) >=
4878 clientUsages
[clientType
] = usageInfo
.TotalUsage();
4880 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
4881 const nsCOMPtr
<nsIConsoleService
> console
=
4882 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
4884 console
->LogStringMessage(
4886 u
"QuotaManager warning: client "_ns
+
4888 u
" reported negative usage for group "_ns
+
4889 NS_ConvertUTF8toUTF16(
4890 aOriginMetadata
.mGroup
) +
4892 NS_ConvertUTF8toUTF16(
4893 aOriginMetadata
.mOrigin
))
4900 QM_TRY((*mClients
)[clientType
]
4901 ->InitOriginWithoutTracking(
4902 aPersistenceType
, aOriginMetadata
,
4903 /* aCanceled */ Atomic
<bool>(false)));
4909 case nsIFileKind::ExistsAsFile
:
4910 if (IsOriginMetadata(leafName
)) {
4914 if (IsTempMetadata(leafName
)) {
4915 QM_TRY(file
->Remove(/* recursive */ false));
4920 if (IsOSMetadata(leafName
) || IsDotFile(leafName
)) {
4924 // Unknown files during initialization are now allowed.
4925 // Just warn if we find them.
4926 UNKNOWN_FILE_WARNING(leafName
);
4927 // Bug 1595448 will handle the case for unknown files
4928 // like idb, cache, or ls.
4931 case nsIFileKind::DoesNotExist
:
4932 // Ignore files that got removed externally while
4939 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
4943 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
4948 #ifdef NIGHTLY_BUILD
4949 if (NS_FAILED(statusKeeper
)) {
4950 return statusKeeper
;
4955 const auto usage
= std::accumulate(
4956 clientUsages
.cbegin(), clientUsages
.cend(), CheckedUint64(0),
4957 [](CheckedUint64 value
, const Maybe
<uint64_t>& clientUsage
) {
4958 return value
+ clientUsage
.valueOr(0);
4961 // XXX Should we log more information, i.e. the whole clientUsages array, in
4962 // case usage is not valid?
4964 QM_TRY(OkIf(usage
.isValid()), NS_ERROR_FAILURE
);
4967 FullOriginMetadata
{aOriginMetadata
, aPersisted
, aAccessTime
},
4968 clientUsages
, usage
.value());
4975 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
4976 nsIFile
* aIndexedDBDir
) {
4977 AssertIsOnIOThread();
4978 MOZ_ASSERT(aIndexedDBDir
);
4980 auto rv
= [this, &aIndexedDBDir
]() -> nsresult
{
4982 QM_TRY(aIndexedDBDir
->IsDirectory(&isDirectory
));
4985 NS_WARNING("indexedDB entry is not a directory!");
4989 auto persistentStorageDirOrErr
= QM_NewLocalFile(*mStoragePath
);
4990 if (NS_WARN_IF(persistentStorageDirOrErr
.isErr())) {
4991 return persistentStorageDirOrErr
.unwrapErr();
4994 nsCOMPtr
<nsIFile
> persistentStorageDir
= persistentStorageDirOrErr
.unwrap();
4996 QM_TRY(persistentStorageDir
->Append(
4997 nsLiteralString(PERSISTENT_DIRECTORY_NAME
)));
5000 QM_TRY(persistentStorageDir
->Exists(&exists
));
5003 QM_WARNING("Deleting old <profile>/indexedDB directory!");
5005 QM_TRY(aIndexedDBDir
->Remove(/* aRecursive */ true));
5010 nsCOMPtr
<nsIFile
> storageDir
;
5011 QM_TRY(persistentStorageDir
->GetParent(getter_AddRefs(storageDir
)));
5013 // MoveTo() is atomic if the move happens on the same volume which should
5014 // be our case, so even if we crash in the middle of the operation nothing
5015 // breaks next time we try to initialize.
5016 // However there's a theoretical possibility that the indexedDB directory
5017 // is on different volume, but it should be rare enough that we don't have
5018 // to worry about it.
5019 QM_TRY(aIndexedDBDir
->MoveTo(storageDir
,
5020 nsLiteralString(PERSISTENT_DIRECTORY_NAME
)));
5025 mInitializationInfo
.RecordFirstInitializationAttempt(
5026 Initialization::UpgradeFromIndexedDBDirectory
, rv
);
5032 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
5033 nsIFile
* aPersistentStorageDir
) {
5034 AssertIsOnIOThread();
5035 MOZ_ASSERT(aPersistentStorageDir
);
5037 auto rv
= [this, &aPersistentStorageDir
]() -> nsresult
{
5038 QM_TRY_INSPECT(const bool& isDirectory
,
5039 MOZ_TO_RESULT_INVOKE(aPersistentStorageDir
, IsDirectory
));
5042 NS_WARNING("persistent entry is not a directory!");
5047 QM_TRY_INSPECT(const auto& defaultStorageDir
,
5048 QM_NewLocalFile(*mDefaultStoragePath
));
5050 QM_TRY_INSPECT(const bool& exists
,
5051 MOZ_TO_RESULT_INVOKE(defaultStorageDir
, Exists
));
5054 QM_WARNING("Deleting old <profile>/storage/persistent directory!");
5056 QM_TRY(aPersistentStorageDir
->Remove(/* aRecursive */ true));
5063 // Create real metadata files for origin directories in persistent
5065 auto helper
= MakeRefPtr
<CreateOrUpgradeDirectoryMetadataHelper
>(
5066 aPersistentStorageDir
);
5068 QM_TRY(helper
->Init());
5070 QM_TRY(helper
->ProcessRepository());
5072 // Upgrade metadata files for origin directories in temporary storage.
5073 QM_TRY_INSPECT(const auto& temporaryStorageDir
,
5074 QM_NewLocalFile(*mTemporaryStoragePath
));
5076 QM_TRY_INSPECT(const bool& exists
,
5077 MOZ_TO_RESULT_INVOKE(temporaryStorageDir
, Exists
));
5080 QM_TRY_INSPECT(const bool& isDirectory
,
5081 MOZ_TO_RESULT_INVOKE(temporaryStorageDir
, IsDirectory
));
5084 NS_WARNING("temporary entry is not a directory!");
5088 helper
= MakeRefPtr
<CreateOrUpgradeDirectoryMetadataHelper
>(
5089 temporaryStorageDir
);
5091 QM_TRY(helper
->Init());
5093 QM_TRY(helper
->ProcessRepository());
5097 // And finally rename persistent to default.
5098 QM_TRY(aPersistentStorageDir
->RenameTo(
5099 nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME
)));
5104 mInitializationInfo
.RecordFirstInitializationAttempt(
5105 Initialization::UpgradeFromPersistentStorageDirectory
, rv
);
5110 template <typename Helper
>
5111 nsresult
QuotaManager::UpgradeStorage(const int32_t aOldVersion
,
5112 const int32_t aNewVersion
,
5113 mozIStorageConnection
* aConnection
) {
5114 AssertIsOnIOThread();
5115 MOZ_ASSERT(aNewVersion
> aOldVersion
);
5116 MOZ_ASSERT(aNewVersion
<= kStorageVersion
);
5117 MOZ_ASSERT(aConnection
);
5119 for (const PersistenceType persistenceType
: kAllPersistenceTypes
) {
5120 QM_TRY_UNWRAP(auto directory
,
5121 QM_NewLocalFile(GetStoragePath(persistenceType
)));
5123 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(directory
, Exists
));
5129 RefPtr
<UpgradeStorageHelperBase
> helper
= new Helper(directory
);
5131 QM_TRY(helper
->Init());
5133 QM_TRY(helper
->ProcessRepository());
5138 QM_TRY_INSPECT(const int32_t& storageVersion
,
5139 MOZ_TO_RESULT_INVOKE(aConnection
, GetSchemaVersion
));
5141 MOZ_ASSERT(storageVersion
== aOldVersion
);
5145 QM_TRY(aConnection
->SetSchemaVersion(aNewVersion
));
5150 nsresult
QuotaManager::UpgradeStorageFrom0_0To1_0(
5151 mozIStorageConnection
* aConnection
) {
5152 AssertIsOnIOThread();
5153 MOZ_ASSERT(aConnection
);
5155 auto rv
= [this, &aConnection
]() -> nsresult
{
5156 QM_TRY(UpgradeStorage
<UpgradeStorageFrom0_0To1_0Helper
>(
5157 0, MakeStorageVersion(1, 0), aConnection
));
5162 mInitializationInfo
.RecordFirstInitializationAttempt(
5163 Initialization::UpgradeStorageFrom0_0To1_0
, rv
);
5168 nsresult
QuotaManager::UpgradeStorageFrom1_0To2_0(
5169 mozIStorageConnection
* aConnection
) {
5170 AssertIsOnIOThread();
5171 MOZ_ASSERT(aConnection
);
5173 // The upgrade consists of a number of logically distinct bugs that
5174 // intentionally got fixed at the same time to trigger just one major
5178 // Morgue directory cleanup
5180 // The original bug that added "on demand" morgue cleanup is 1165119.
5183 // Morgue directories are removed from all origin directories during the
5184 // upgrade process. Origin initialization and usage calculation doesn't try
5185 // to remove morgue directories anymore.
5187 // [Downgrade-incompatible changes]:
5188 // Morgue directories can reappear if user runs an already upgraded profile
5189 // in an older version of Firefox. Morgue directories then prevent current
5190 // Firefox from initializing and using the storage.
5195 // The bug that removes isApp flags is 1311057.
5198 // Origin directories with appIds are removed during the upgrade process.
5200 // [Downgrade-incompatible changes]:
5201 // Origin directories with appIds can reappear if user runs an already
5202 // upgraded profile in an older version of Firefox. Origin directories with
5203 // appIds don't prevent current Firefox from initializing and using the
5204 // storage, but they wouldn't ever be removed again, potentially causing
5205 // problems once appId is removed from origin attributes.
5208 // Strip obsolete origin attributes
5210 // The bug that strips obsolete origin attributes is 1314361.
5213 // Origin directories with obsolete origin attributes are renamed and their
5214 // metadata files are updated during the upgrade process.
5216 // [Downgrade-incompatible changes]:
5217 // Origin directories with obsolete origin attributes can reappear if user
5218 // runs an already upgraded profile in an older version of Firefox. Origin
5219 // directories with obsolete origin attributes don't prevent current Firefox
5220 // from initializing and using the storage, but they wouldn't ever be upgraded
5221 // again, potentially causing problems in future.
5224 // File manager directory renaming (client specific)
5226 // The original bug that added "on demand" file manager directory renaming is
5230 // All file manager directories are renamed to contain the ".files" suffix.
5232 // [Downgrade-incompatible changes]:
5233 // File manager directories with the ".files" suffix prevent older versions of
5234 // Firefox from initializing and using the storage.
5235 // File manager directories without the ".files" suffix can appear if user
5236 // runs an already upgraded profile in an older version of Firefox. File
5237 // manager directories without the ".files" suffix then prevent current
5238 // Firefox from initializing and using the storage.
5240 auto rv
= [this, &aConnection
]() -> nsresult
{
5241 QM_TRY(UpgradeStorage
<UpgradeStorageFrom1_0To2_0Helper
>(
5242 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection
));
5247 mInitializationInfo
.RecordFirstInitializationAttempt(
5248 Initialization::UpgradeStorageFrom1_0To2_0
, rv
);
5253 nsresult
QuotaManager::UpgradeStorageFrom2_0To2_1(
5254 mozIStorageConnection
* aConnection
) {
5255 AssertIsOnIOThread();
5256 MOZ_ASSERT(aConnection
);
5258 // The upgrade is mainly to create a directory padding file in DOM Cache
5259 // directory to record the overall padding size of an origin.
5261 auto rv
= [this, &aConnection
]() -> nsresult
{
5262 QM_TRY(UpgradeStorage
<UpgradeStorageFrom2_0To2_1Helper
>(
5263 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection
));
5268 mInitializationInfo
.RecordFirstInitializationAttempt(
5269 Initialization::UpgradeStorageFrom2_0To2_1
, rv
);
5274 nsresult
QuotaManager::UpgradeStorageFrom2_1To2_2(
5275 mozIStorageConnection
* aConnection
) {
5276 AssertIsOnIOThread();
5277 MOZ_ASSERT(aConnection
);
5279 // The upgrade is mainly to clean obsolete origins in the repositoies, remove
5280 // asmjs client, and ".tmp" file in the idb folers.
5282 auto rv
= [this, &aConnection
]() -> nsresult
{
5283 QM_TRY(UpgradeStorage
<UpgradeStorageFrom2_1To2_2Helper
>(
5284 MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection
));
5289 mInitializationInfo
.RecordFirstInitializationAttempt(
5290 Initialization::UpgradeStorageFrom2_1To2_2
, rv
);
5295 nsresult
QuotaManager::UpgradeStorageFrom2_2To2_3(
5296 mozIStorageConnection
* aConnection
) {
5297 AssertIsOnIOThread();
5298 MOZ_ASSERT(aConnection
);
5300 auto rv
= [&aConnection
]() -> nsresult
{
5302 QM_TRY(aConnection
->ExecuteSimpleSQL(
5303 nsLiteralCString("CREATE TABLE database"
5304 "( cache_version INTEGER NOT NULL DEFAULT 0"
5307 QM_TRY(aConnection
->ExecuteSimpleSQL(
5308 nsLiteralCString("INSERT INTO database (cache_version) "
5313 QM_TRY_INSPECT(const int32_t& storageVersion
,
5314 MOZ_TO_RESULT_INVOKE(aConnection
, GetSchemaVersion
));
5316 MOZ_ASSERT(storageVersion
== MakeStorageVersion(2, 2));
5320 QM_TRY(aConnection
->SetSchemaVersion(MakeStorageVersion(2, 3)));
5325 mInitializationInfo
.RecordFirstInitializationAttempt(
5326 Initialization::UpgradeStorageFrom2_2To2_3
, rv
);
5331 nsresult
QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
5332 nsIFile
& aLsArchiveFile
) {
5333 AssertIsOnIOThread();
5334 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
5336 QM_TRY_INSPECT(const bool& exists
,
5337 MOZ_TO_RESULT_INVOKE(aLsArchiveFile
, Exists
));
5340 // If the ls archive doesn't exist then ls directories can't exist either.
5344 QM_TRY(MaybeRemoveLocalStorageDirectories());
5346 InvalidateQuotaCache();
5348 // Finally remove the ls archive, so we don't have to check all origin
5349 // directories next time this method is called.
5350 QM_TRY(aLsArchiveFile
.Remove(false));
5355 nsresult
QuotaManager::MaybeRemoveLocalStorageDirectories() {
5356 AssertIsOnIOThread();
5358 QM_TRY_INSPECT(const auto& defaultStorageDir
,
5359 QM_NewLocalFile(*mDefaultStoragePath
));
5361 QM_TRY_INSPECT(const bool& exists
,
5362 MOZ_TO_RESULT_INVOKE(defaultStorageDir
, Exists
));
5368 QM_TRY(CollectEachFile(
5370 [](const nsCOMPtr
<nsIFile
>& originDir
) -> Result
<Ok
, nsresult
> {
5373 QM_TRY_INSPECT(const bool& exists
,
5374 MOZ_TO_RESULT_INVOKE(originDir
, Exists
));
5379 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*originDir
));
5381 switch (dirEntryKind
) {
5382 case nsIFileKind::ExistsAsDirectory
: {
5385 CloneFileAndAppend(*originDir
, NS_LITERAL_STRING_FROM_CSTRING(
5386 LS_DIRECTORY_NAME
)));
5389 QM_TRY_INSPECT(const bool& exists
,
5390 MOZ_TO_RESULT_INVOKE(lsDir
, Exists
));
5398 QM_TRY_INSPECT(const bool& isDirectory
,
5399 MOZ_TO_RESULT_INVOKE(lsDir
, IsDirectory
));
5402 QM_WARNING("ls entry is not a directory!");
5409 QM_TRY(lsDir
->GetPath(path
));
5411 QM_WARNING("Deleting %s directory!",
5412 NS_ConvertUTF16toUTF8(path
).get());
5414 QM_TRY(lsDir
->Remove(/* aRecursive */ true));
5419 case nsIFileKind::ExistsAsFile
: {
5420 QM_TRY_INSPECT(const auto& leafName
,
5421 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, originDir
,
5424 // Unknown files during upgrade are allowed. Just warn if we find
5426 if (!IsOSMetadata(leafName
)) {
5427 UNKNOWN_FILE_WARNING(leafName
);
5433 case nsIFileKind::DoesNotExist
:
5434 // Ignore files that got removed externally while iterating.
5443 Result
<Ok
, nsresult
> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
5444 nsIFile
& aLsArchiveFile
) const {
5445 AssertIsOnIOThread();
5446 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5450 QM_TRY_INSPECT(const bool& exists
,
5451 MOZ_TO_RESULT_INVOKE(aLsArchiveFile
, Exists
));
5452 MOZ_ASSERT(!exists
);
5456 // Get the storage service first, we will need it at multiple places.
5457 QM_TRY_INSPECT(const auto& ss
, ToResultGet
<nsCOMPtr
<mozIStorageService
>>(
5458 MOZ_SELECT_OVERLOAD(do_GetService
),
5459 MOZ_STORAGE_SERVICE_CONTRACTID
));
5461 // Get the web apps store file.
5462 QM_TRY_INSPECT(const auto& webAppsStoreFile
, QM_NewLocalFile(mBasePath
));
5464 QM_TRY(webAppsStoreFile
->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME
)));
5466 // Now check if the web apps store is useable.
5467 QM_TRY_INSPECT(const auto& connection
,
5468 CreateWebAppsStoreConnection(*webAppsStoreFile
, *ss
));
5471 // Find out the journal mode.
5472 QM_TRY_INSPECT(const auto& stmt
,
5473 CreateAndExecuteSingleStepStatement(
5474 *connection
, "PRAGMA journal_mode;"_ns
));
5477 const auto& journalMode
,
5478 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoCString
, *stmt
, GetUTF8String
, 0));
5480 QM_TRY(stmt
->Finalize());
5482 if (journalMode
.EqualsLiteral("wal")) {
5483 // We don't copy the WAL file, so make sure the old database is fully
5486 connection
->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns
));
5489 // Explicitely close the connection before the old database is copied.
5490 QM_TRY(connection
->Close());
5492 // Copy the old database. The database is copied from
5493 // <profile>/webappsstore.sqlite to
5494 // <profile>/storage/ls-archive-tmp.sqlite
5495 // We use a "-tmp" postfix since we are not done yet.
5496 QM_TRY_INSPECT(const auto& storageDir
, QM_NewLocalFile(*mStoragePath
));
5498 QM_TRY(webAppsStoreFile
->CopyTo(storageDir
,
5499 nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME
)));
5501 QM_TRY_INSPECT(const auto& lsArchiveTmpFile
,
5502 GetLocalStorageArchiveTmpFile(*mStoragePath
));
5504 if (journalMode
.EqualsLiteral("wal")) {
5506 const auto& lsArchiveTmpConnection
,
5507 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<mozIStorageConnection
>, ss
,
5508 OpenUnsharedDatabase
, lsArchiveTmpFile
));
5510 // The archive will only be used for lazy data migration. There won't be
5511 // any concurrent readers and writers that could benefit from Write-Ahead
5512 // Logging. So switch to a standard rollback journal. The standard
5513 // rollback journal also provides atomicity across multiple attached
5514 // databases which is import for the lazy data migration to work safely.
5515 QM_TRY(lsArchiveTmpConnection
->ExecuteSimpleSQL(
5516 "PRAGMA journal_mode = DELETE;"_ns
));
5518 // Close the connection explicitly. We are going to rename the file below.
5519 QM_TRY(lsArchiveTmpConnection
->Close());
5522 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
5523 QM_TRY(lsArchiveTmpFile
->MoveTo(nullptr,
5524 nsLiteralString(LS_ARCHIVE_FILE_NAME
)));
5529 // If webappsstore database is not useable, just create an empty archive.
5530 // XXX The code below should be removed and the caller should call us only
5531 // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
5532 // reworked to propagate database corruption instead of returning null
5534 // So, if there's no webappsstore.sqlite
5535 // MaybeCreateOrUpgradeLocalStorageArchive will call
5536 // CreateEmptyLocalStorageArchive instead of
5537 // CopyLocalStorageArchiveFromWebAppsStore.
5538 // If there's any corruption detected during
5539 // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
5540 // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
5541 // EnsureStorageIsInitialized will fallback to
5542 // CreateEmptyLocalStorageArchive.
5544 // Ensure the storage directory actually exists.
5545 QM_TRY_INSPECT(const auto& storageDirectory
, QM_NewLocalFile(*mStoragePath
));
5547 QM_TRY_INSPECT(const bool& created
, EnsureDirectory(*storageDirectory
));
5552 auto lsArchiveConnection
,
5553 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<mozIStorageConnection
>, ss
,
5554 OpenUnsharedDatabase
, &aLsArchiveFile
));
5556 QM_TRY(StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection
));
5561 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
5562 QuotaManager::CreateLocalStorageArchiveConnection(
5563 nsIFile
& aLsArchiveFile
) const {
5564 AssertIsOnIOThread();
5565 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5569 QM_TRY_INSPECT(const bool& exists
,
5570 MOZ_TO_RESULT_INVOKE(aLsArchiveFile
, Exists
));
5575 QM_TRY_INSPECT(const bool& isDirectory
,
5576 MOZ_TO_RESULT_INVOKE(aLsArchiveFile
, IsDirectory
));
5578 // A directory with the name of the archive file is treated as corruption
5579 // (similarly as wrong content of the file).
5580 QM_TRY(OkIf(!isDirectory
), Err(NS_ERROR_FILE_CORRUPTED
));
5582 QM_TRY_INSPECT(const auto& ss
, ToResultGet
<nsCOMPtr
<mozIStorageService
>>(
5583 MOZ_SELECT_OVERLOAD(do_GetService
),
5584 MOZ_STORAGE_SERVICE_CONTRACTID
));
5586 // This may return NS_ERROR_FILE_CORRUPTED too.
5587 QM_TRY_UNWRAP(auto connection
, MOZ_TO_RESULT_INVOKE_TYPED(
5588 nsCOMPtr
<mozIStorageConnection
>, ss
,
5589 OpenUnsharedDatabase
, &aLsArchiveFile
));
5591 // The legacy LS implementation removes the database and creates an empty one
5592 // when the schema can't be updated. The same effect can be achieved here by
5593 // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
5594 // sub test case 3 of dom/localstorage/test/unit/test_archive.js
5596 ToResult(StorageDBUpdater::Update(connection
))
5597 .mapErr([](const nsresult rv
) { return NS_ERROR_FILE_CORRUPTED
; }));
5602 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
5603 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
5604 nsIFile
& aLsArchiveFile
) {
5605 AssertIsOnIOThread();
5606 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5608 QM_TRY(MaybeRemoveLocalStorageDirectories());
5612 QM_TRY_INSPECT(const bool& exists
,
5613 MOZ_TO_RESULT_INVOKE(aLsArchiveFile
, Exists
));
5619 QM_TRY(aLsArchiveFile
.Remove(false));
5621 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
5623 QM_TRY_UNWRAP(auto connection
,
5624 CreateLocalStorageArchiveConnection(aLsArchiveFile
));
5626 QM_TRY(InitializeLocalStorageArchive(connection
));
5631 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
5632 QuotaManager::DowngradeLocalStorageArchive(nsIFile
& aLsArchiveFile
) {
5633 AssertIsOnIOThread();
5634 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5636 QM_TRY_UNWRAP(auto connection
,
5637 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
5640 SaveLocalStorageArchiveVersion(connection
, kLocalStorageArchiveVersion
));
5645 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
5646 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
5647 nsIFile
& aLsArchiveFile
) {
5648 AssertIsOnIOThread();
5649 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5651 QM_TRY_UNWRAP(auto connection
,
5652 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
5654 QM_TRY(SaveLocalStorageArchiveVersion(connection
, 4));
5660 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
5661 nsCOMPtr<mozIStorageConnection>& aConnection) {
5662 AssertIsOnIOThread();
5663 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5665 nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
5666 if (NS_WARN_IF(NS_FAILED(rv))) {
5676 void QuotaManager::AssertStorageIsInitialized() const {
5677 AssertIsOnIOThread();
5678 MOZ_ASSERT(IsStorageInitialized());
5683 nsresult
QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
5684 nsIFile
& aStorageFile
) {
5685 AssertIsOnIOThread();
5687 QM_TRY_INSPECT(const auto& storageFileExists
,
5688 MOZ_TO_RESULT_INVOKE(aStorageFile
, Exists
));
5690 if (!storageFileExists
) {
5691 QM_TRY_INSPECT(const auto& indexedDBDir
, QM_NewLocalFile(*mIndexedDBPath
));
5693 QM_TRY_INSPECT(const auto& indexedDBDirExists
,
5694 MOZ_TO_RESULT_INVOKE(indexedDBDir
, Exists
));
5696 if (indexedDBDirExists
) {
5697 QM_TRY(UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
5701 QM_TRY_INSPECT(const auto& persistentStorageDir
,
5702 QM_NewLocalFile(*mStoragePath
));
5704 QM_TRY(persistentStorageDir
->Append(
5705 nsLiteralString(PERSISTENT_DIRECTORY_NAME
)));
5707 QM_TRY_INSPECT(const auto& persistentStorageDirExists
,
5708 MOZ_TO_RESULT_INVOKE(persistentStorageDir
, Exists
));
5710 if (persistentStorageDirExists
) {
5711 QM_TRY(UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
5712 persistentStorageDir
));
5719 nsresult
QuotaManager::MaybeCreateOrUpgradeStorage(
5720 mozIStorageConnection
& aConnection
) {
5721 AssertIsOnIOThread();
5723 QM_TRY_UNWRAP(auto storageVersion
,
5724 MOZ_TO_RESULT_INVOKE(aConnection
, GetSchemaVersion
));
5726 // Hacky downgrade logic!
5727 // If we see major.minor of 3.0, downgrade it to be 2.1.
5728 if (storageVersion
== kHackyPreDowngradeStorageVersion
) {
5729 storageVersion
= kHackyPostDowngradeStorageVersion
;
5730 QM_TRY(aConnection
.SetSchemaVersion(storageVersion
), QM_PROPAGATE
,
5731 [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
5734 QM_TRY(OkIf(GetMajorStorageVersion(storageVersion
) <= kMajorStorageVersion
),
5735 NS_ERROR_FAILURE
, [](const auto&) {
5736 NS_WARNING("Unable to initialize storage, version is too high!");
5739 if (storageVersion
< kStorageVersion
) {
5740 const bool newDatabase
= !storageVersion
;
5742 QM_TRY_INSPECT(const auto& storageDir
, QM_NewLocalFile(*mStoragePath
));
5744 QM_TRY_INSPECT(const auto& storageDirExists
,
5745 MOZ_TO_RESULT_INVOKE(storageDir
, Exists
));
5747 const bool newDirectory
= !storageDirExists
;
5750 // Set the page size first.
5751 if (kSQLitePageSizeOverride
) {
5752 QM_TRY(aConnection
.ExecuteSimpleSQL(nsPrintfCString(
5753 "PRAGMA page_size = %" PRIu32
";", kSQLitePageSizeOverride
)));
5757 mozStorageTransaction
transaction(
5758 &aConnection
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
5760 QM_TRY(transaction
.Start());
5762 // An upgrade method can upgrade the database, the storage or both.
5763 // The upgrade loop below can only be avoided when there's no database and
5764 // no storage yet (e.g. new profile).
5765 if (newDatabase
&& newDirectory
) {
5766 QM_TRY(CreateTables(&aConnection
));
5770 QM_TRY_INSPECT(const int32_t& storageVersion
,
5771 MOZ_TO_RESULT_INVOKE(aConnection
, GetSchemaVersion
),
5772 QM_ASSERT_UNREACHABLE
);
5773 MOZ_ASSERT(storageVersion
== kStorageVersion
);
5777 QM_TRY(aConnection
.ExecuteSimpleSQL(
5778 nsLiteralCString("INSERT INTO database (cache_version) "
5781 // This logic needs to change next time we change the storage!
5782 static_assert(kStorageVersion
== int32_t((2 << 16) + 3),
5783 "Upgrade function needed due to storage version increase.");
5785 while (storageVersion
!= kStorageVersion
) {
5786 if (storageVersion
== 0) {
5787 QM_TRY(UpgradeStorageFrom0_0To1_0(&aConnection
));
5788 } else if (storageVersion
== MakeStorageVersion(1, 0)) {
5789 QM_TRY(UpgradeStorageFrom1_0To2_0(&aConnection
));
5790 } else if (storageVersion
== MakeStorageVersion(2, 0)) {
5791 QM_TRY(UpgradeStorageFrom2_0To2_1(&aConnection
));
5792 } else if (storageVersion
== MakeStorageVersion(2, 1)) {
5793 QM_TRY(UpgradeStorageFrom2_1To2_2(&aConnection
));
5794 } else if (storageVersion
== MakeStorageVersion(2, 2)) {
5795 QM_TRY(UpgradeStorageFrom2_2To2_3(&aConnection
));
5797 QM_FAIL(NS_ERROR_FAILURE
, []() {
5799 "Unable to initialize storage, no upgrade path is "
5804 QM_TRY_UNWRAP(storageVersion
,
5805 MOZ_TO_RESULT_INVOKE(aConnection
, GetSchemaVersion
));
5808 MOZ_ASSERT(storageVersion
== kStorageVersion
);
5811 QM_TRY(transaction
.Commit());
5817 nsresult
QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
5818 AssertIsOnIOThread();
5820 QM_TRY_INSPECT(const auto& lsArchiveTmpFile
,
5821 GetLocalStorageArchiveTmpFile(*mStoragePath
));
5823 QM_TRY_INSPECT(const bool& exists
,
5824 MOZ_TO_RESULT_INVOKE(lsArchiveTmpFile
, Exists
));
5827 QM_TRY(lsArchiveTmpFile
->Remove(false));
5833 Result
<Ok
, nsresult
> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
5834 nsIFile
& aLsArchiveFile
) {
5835 AssertIsOnIOThread();
5838 const bool& lsArchiveFileExisted
,
5839 ([this, &aLsArchiveFile
]() -> Result
<bool, nsresult
> {
5840 QM_TRY_INSPECT(const bool& exists
,
5841 MOZ_TO_RESULT_INVOKE(aLsArchiveFile
, Exists
));
5844 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
5850 QM_TRY_UNWRAP(auto connection
,
5851 CreateLocalStorageArchiveConnection(aLsArchiveFile
));
5853 QM_TRY_INSPECT(const auto& initialized
,
5854 IsLocalStorageArchiveInitialized(*connection
));
5857 QM_TRY(InitializeLocalStorageArchive(connection
));
5860 QM_TRY_UNWRAP(int32_t version
, LoadLocalStorageArchiveVersion(*connection
));
5862 if (version
> kLocalStorageArchiveVersion
) {
5863 // Close local storage archive connection. We are going to remove underlying
5865 QM_TRY(connection
->Close());
5867 // This will wipe the archive and any migrated data and recopy the archive
5868 // from webappsstore.sqlite.
5869 QM_TRY_UNWRAP(connection
, DowngradeLocalStorageArchive(aLsArchiveFile
));
5871 QM_TRY_UNWRAP(version
, LoadLocalStorageArchiveVersion(*connection
));
5873 MOZ_ASSERT(version
== kLocalStorageArchiveVersion
);
5874 } else if (version
!= kLocalStorageArchiveVersion
) {
5875 // The version can be zero either when the archive didn't exist or it did
5876 // exist, but the archive was created without any version information.
5877 // We don't need to do any upgrades only if it didn't exist because existing
5878 // archives without version information must be recopied to really fix bug
5879 // 1542104. See also bug 1546305 which introduced archive versions.
5880 if (!lsArchiveFileExisted
) {
5881 MOZ_ASSERT(version
== 0);
5883 QM_TRY(SaveLocalStorageArchiveVersion(connection
,
5884 kLocalStorageArchiveVersion
));
5886 static_assert(kLocalStorageArchiveVersion
== 4,
5887 "Upgrade function needed due to LocalStorage archive "
5888 "version increase.");
5890 while (version
!= kLocalStorageArchiveVersion
) {
5892 // Close local storage archive connection. We are going to remove
5894 QM_TRY(connection
->Close());
5896 // This won't do an "upgrade" in a normal sense. It will wipe the
5897 // archive and any migrated data and recopy the archive from
5898 // webappsstore.sqlite
5899 QM_TRY_UNWRAP(connection
, UpgradeLocalStorageArchiveFromLessThan4To4(
5901 } /* else if (version == 4) {
5902 QM_TRY(UpgradeLocalStorageArchiveFrom4To5(connection));
5905 QM_FAIL(Err(NS_ERROR_FAILURE
), []() {
5907 "Unable to initialize LocalStorage archive, no upgrade path "
5912 QM_TRY_UNWRAP(version
, LoadLocalStorageArchiveVersion(*connection
));
5915 MOZ_ASSERT(version
== kLocalStorageArchiveVersion
);
5919 // At this point, we have finished initializing the local storage archive, and
5920 // can continue storage initialization. We don't know though if the actual
5921 // data in the archive file is readable. We can't do a PRAGMA integrity_check
5922 // here though, because that would be too heavyweight.
5927 Result
<Ok
, nsresult
> QuotaManager::CreateEmptyLocalStorageArchive(
5928 nsIFile
& aLsArchiveFile
) const {
5929 AssertIsOnIOThread();
5931 QM_TRY_INSPECT(const bool& exists
,
5932 MOZ_TO_RESULT_INVOKE(aLsArchiveFile
, Exists
));
5934 // If it exists, remove it. It might be a directory, so remove it recursively.
5936 QM_TRY(aLsArchiveFile
.Remove(true));
5938 // XXX If we crash right here, the next session will copy the archive from
5939 // webappsstore.sqlite again!
5940 // XXX Create a marker file before removing the archive which can be
5941 // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
5942 // archive instead of recopying it from webapppstore.sqlite (in other
5943 // words, finishing what was started here).
5946 QM_TRY_INSPECT(const auto& ss
, ToResultGet
<nsCOMPtr
<mozIStorageService
>>(
5947 MOZ_SELECT_OVERLOAD(do_GetService
),
5948 MOZ_STORAGE_SERVICE_CONTRACTID
));
5951 const auto connection
,
5952 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<mozIStorageConnection
>, ss
,
5953 OpenUnsharedDatabase
, &aLsArchiveFile
));
5955 QM_TRY(StorageDBUpdater::CreateCurrentSchema(connection
));
5957 QM_TRY(InitializeLocalStorageArchive(connection
));
5960 SaveLocalStorageArchiveVersion(connection
, kLocalStorageArchiveVersion
));
5965 nsresult
QuotaManager::EnsureStorageIsInitialized() {
5966 AssertIsOnIOThread();
5968 if (mStorageConnection
) {
5969 mInitializationInfo
.AssertInitializationAttempted(Initialization::Storage
);
5973 const auto autoRecord
= mInitializationInfo
.RecordFirstInitializationAttempt(
5974 Initialization::Storage
,
5975 [&self
= *this] { return static_cast<bool>(self
.mStorageConnection
); });
5977 const auto contextLogExtraInfo
=
5978 autoRecord
.IsFirstInitializationAttempt()
5979 ? Some(ScopedLogExtraInfo
{ScopedLogExtraInfo::kTagContext
,
5980 "Initialization::Storage"_ns
})
5983 QM_TRY_INSPECT(const auto& storageFile
, QM_NewLocalFile(mBasePath
));
5984 QM_TRY(storageFile
->Append(mStorageName
+ kSQLiteSuffix
));
5986 QM_TRY(MaybeUpgradeToDefaultStorageDirectory(*storageFile
));
5988 QM_TRY_INSPECT(const auto& ss
, ToResultGet
<nsCOMPtr
<mozIStorageService
>>(
5989 MOZ_SELECT_OVERLOAD(do_GetService
),
5990 MOZ_STORAGE_SERVICE_CONTRACTID
));
5995 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<mozIStorageConnection
>, ss
,
5996 OpenUnsharedDatabase
, storageFile
),
5997 (FilterDatabaseCorruptionError
<nullptr,
5998 nsCOMPtr
<mozIStorageConnection
>>)));
6001 // Nuke the database file.
6002 QM_TRY(storageFile
->Remove(false));
6004 QM_TRY_UNWRAP(connection
, MOZ_TO_RESULT_INVOKE_TYPED(
6005 nsCOMPtr
<mozIStorageConnection
>, ss
,
6006 OpenUnsharedDatabase
, storageFile
));
6009 // We want extra durability for this important file.
6010 QM_TRY(connection
->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns
));
6012 // Check to make sure that the storage version is correct.
6013 QM_TRY(MaybeCreateOrUpgradeStorage(*connection
));
6015 QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
6017 QM_TRY_INSPECT(const auto& lsArchiveFile
,
6018 GetLocalStorageArchiveFile(*mStoragePath
));
6020 if (CachedNextGenLocalStorageEnabled()) {
6021 QM_TRY(QM_OR_ELSE_WARN(
6022 MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile
),
6023 ([&](const nsresult rv
) -> Result
<Ok
, nsresult
> {
6024 if (IsDatabaseCorruptionError(rv
)) {
6025 QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile
));
6030 QM_TRY(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile
));
6033 QM_TRY_UNWRAP(mCacheUsable
, MaybeCreateOrUpgradeCache(*connection
));
6035 if (mCacheUsable
&& gInvalidateQuotaCache
) {
6036 QM_TRY(InvalidateCache(*connection
));
6038 gInvalidateQuotaCache
= false;
6041 mStorageConnection
= std::move(connection
);
6046 RefPtr
<ClientDirectoryLock
> QuotaManager::CreateDirectoryLock(
6047 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
6048 Client::Type aClientType
, bool aExclusive
) {
6049 AssertIsOnOwningThread();
6051 return DirectoryLockImpl::Create(WrapNotNullUnchecked(this), aPersistenceType
,
6052 aOriginMetadata
, aClientType
, aExclusive
);
6055 RefPtr
<UniversalDirectoryLock
> QuotaManager::CreateDirectoryLockInternal(
6056 const Nullable
<PersistenceType
>& aPersistenceType
,
6057 const OriginScope
& aOriginScope
, const Nullable
<Client::Type
>& aClientType
,
6059 AssertIsOnOwningThread();
6061 return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
6062 aPersistenceType
, aOriginScope
,
6063 aClientType
, aExclusive
);
6066 Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
>
6067 QuotaManager::EnsurePersistentOriginIsInitialized(
6068 const OriginMetadata
& aOriginMetadata
) {
6069 AssertIsOnIOThread();
6070 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
);
6071 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection
);
6073 auto res
= [&aOriginMetadata
, this]()
6074 -> mozilla::Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
> {
6075 QM_TRY_UNWRAP(auto directory
,
6076 GetDirectoryForOrigin(PERSISTENCE_TYPE_PERSISTENT
,
6077 aOriginMetadata
.mOrigin
));
6079 if (mInitializedOrigins
.Contains(aOriginMetadata
.mOrigin
)) {
6080 return std::pair(std::move(directory
), false);
6083 QM_TRY_INSPECT(const bool& created
, EnsureOriginDirectory(*directory
));
6085 QM_TRY_INSPECT(const int64_t& timestamp
,
6086 ([this, created
, &directory
,
6087 &aOriginMetadata
]() -> Result
<int64_t, nsresult
> {
6089 const int64_t timestamp
= PR_Now();
6091 // Only creating .metadata-v2 to reduce IO.
6092 QM_TRY(CreateDirectoryMetadata2(*directory
, timestamp
,
6093 /* aPersisted */ true,
6099 // Get the metadata. We only use the timestamp.
6101 const auto& metadata
,
6102 LoadFullOriginMetadataWithRestore(directory
));
6104 MOZ_ASSERT(metadata
.mLastAccessTime
<= PR_Now());
6106 return metadata
.mLastAccessTime
;
6109 QM_TRY(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT
, aOriginMetadata
,
6111 /* aPersisted */ true, directory
));
6113 mInitializedOrigins
.AppendElement(aOriginMetadata
.mOrigin
);
6115 return std::pair(std::move(directory
), created
);
6119 mOriginInitializationInfos
.LookupOrInsert(aOriginMetadata
.mOrigin
);
6120 !info
.mPersistentOriginAttempted
) {
6121 Telemetry::Accumulate(Telemetry::QM_FIRST_INITIALIZATION_ATTEMPT
,
6122 kPersistentOriginTelemetryKey
,
6123 static_cast<uint32_t>(res
.isOk()));
6125 info
.mPersistentOriginAttempted
= true;
6131 Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
>
6132 QuotaManager::EnsureTemporaryOriginIsInitialized(
6133 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
) {
6134 AssertIsOnIOThread();
6135 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6136 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection
);
6137 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitialized
);
6139 auto res
= [&aPersistenceType
, &aOriginMetadata
, this]()
6140 -> mozilla::Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
> {
6141 // Get directory for this origin and persistence type.
6144 GetDirectoryForOrigin(aPersistenceType
, aOriginMetadata
.mOrigin
));
6146 QM_TRY_INSPECT(const bool& created
, EnsureOriginDirectory(*directory
));
6149 const int64_t timestamp
=
6150 NoteOriginDirectoryCreated(aOriginMetadata
, /* aPersisted */ false);
6152 // Only creating .metadata-v2 to reduce IO.
6153 QM_TRY(CreateDirectoryMetadata2(*directory
, timestamp
,
6154 /* aPersisted */ false, aOriginMetadata
));
6157 // TODO: If the metadata file exists and we didn't call
6158 // LoadFullOriginMetadataWithRestore for it (because the quota info
6159 // was loaded from the cache), then the group in the metadata file
6160 // may be wrong, so it should be checked and eventually updated.
6161 // It's not a big deal that we are not doing it here, because the
6162 // origin will be marked as "accessed", so
6163 // LoadFullOriginMetadataWithRestore will be called for the metadata
6164 // file in next session in LoadQuotaFromCache.
6166 return std::pair(std::move(directory
), created
);
6170 mOriginInitializationInfos
.LookupOrInsert(aOriginMetadata
.mOrigin
);
6171 if (!info
.mTemporaryOriginAttempted
) {
6172 Telemetry::Accumulate(Telemetry::QM_FIRST_INITIALIZATION_ATTEMPT
,
6173 kTemporaryOriginTelemetryKey
,
6174 static_cast<uint32_t>(res
.isOk()));
6176 info
.mTemporaryOriginAttempted
= true;
6182 nsresult
QuotaManager::EnsureTemporaryStorageIsInitialized() {
6183 AssertIsOnIOThread();
6184 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection
);
6186 if (mTemporaryStorageInitialized
) {
6187 mInitializationInfo
.AssertInitializationAttempted(
6188 Initialization::TemporaryStorage
);
6192 const auto autoRecord
= mInitializationInfo
.RecordFirstInitializationAttempt(
6193 Initialization::TemporaryStorage
,
6194 [&self
= *this] { return self
.mTemporaryStorageInitialized
; });
6196 const auto contextLogExtraInfo
=
6197 autoRecord
.IsFirstInitializationAttempt()
6198 ? Some(ScopedLogExtraInfo
{ScopedLogExtraInfo::kTagContext
,
6199 "Initialization::TemporaryStorage"_ns
})
6203 const auto& storageDir
,
6204 ToResultGet
<nsCOMPtr
<nsIFile
>>(MOZ_SELECT_OVERLOAD(do_CreateInstance
),
6205 NS_LOCAL_FILE_CONTRACTID
));
6207 QM_TRY(storageDir
->InitWithPath(GetStoragePath()));
6209 // The storage directory must exist before calling GetDiskSpaceAvailable.
6210 QM_TRY_INSPECT(const bool& created
, EnsureDirectory(*storageDir
));
6214 // Check for available disk space users have on their device where storage
6216 QM_TRY_INSPECT(const int64_t& diskSpaceAvailable
,
6217 MOZ_TO_RESULT_INVOKE(storageDir
, GetDiskSpaceAvailable
));
6219 MOZ_ASSERT(diskSpaceAvailable
>= 0);
6221 QM_TRY(LoadQuota());
6223 mTemporaryStorageInitialized
= true;
6225 // Available disk space shouldn't be used directly for temporary storage
6226 // limit calculation since available disk space is affected by existing data
6227 // stored in temporary storage. So we need to increase it by the temporary
6228 // storage size (that has been calculated in LoadQuota) before passing to
6229 // GetTemporaryStorageLimit..
6230 mTemporaryStorageLimit
= GetTemporaryStorageLimit(
6231 /* aAvailableSpaceBytes */ diskSpaceAvailable
+ mTemporaryStorageUsage
);
6233 CleanupTemporaryStorage();
6236 QM_TRY(InvalidateCache(*mStorageConnection
));
6242 void QuotaManager::ShutdownStorage() {
6243 AssertIsOnIOThread();
6245 if (mStorageConnection
) {
6246 mOriginInitializationInfos
.Clear();
6247 mInitializedOrigins
.Clear();
6249 if (mTemporaryStorageInitialized
) {
6256 mTemporaryStorageInitialized
= false;
6259 ReleaseIOThreadObjects();
6261 mStorageConnection
= nullptr;
6262 mCacheUsable
= false;
6265 mInitializationInfo
.ResetInitializationAttempts();
6268 Result
<bool, nsresult
> QuotaManager::EnsureOriginDirectory(
6269 nsIFile
& aDirectory
) {
6270 AssertIsOnIOThread();
6272 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(aDirectory
, Exists
));
6275 QM_TRY_INSPECT(const auto& leafName
,
6276 MOZ_TO_RESULT_INVOKE_TYPED(nsString
, aDirectory
, GetLeafName
)
6277 .map([](const auto& leafName
) {
6278 return NS_ConvertUTF16toUTF8(leafName
);
6281 QM_TRY(OkIf(IsSanitizedOriginValid(leafName
)), Err(NS_ERROR_FAILURE
),
6284 "Preventing creation of a new origin directory which is not "
6285 "supported by our origin parser or is obsolete!");
6289 QM_TRY_RETURN(EnsureDirectory(aDirectory
));
6292 nsresult
QuotaManager::AboutToClearOrigins(
6293 const Nullable
<PersistenceType
>& aPersistenceType
,
6294 const OriginScope
& aOriginScope
,
6295 const Nullable
<Client::Type
>& aClientType
) {
6296 AssertIsOnIOThread();
6298 if (aClientType
.IsNull()) {
6299 for (Client::Type type
: AllClientTypes()) {
6300 QM_TRY((*mClients
)[type
]->AboutToClearOrigins(aPersistenceType
,
6304 QM_TRY((*mClients
)[aClientType
.Value()]->AboutToClearOrigins(
6305 aPersistenceType
, aOriginScope
));
6311 void QuotaManager::OriginClearCompleted(
6312 PersistenceType aPersistenceType
, const nsACString
& aOrigin
,
6313 const Nullable
<Client::Type
>& aClientType
) {
6314 AssertIsOnIOThread();
6316 if (aClientType
.IsNull()) {
6317 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
6318 mInitializedOrigins
.RemoveElement(aOrigin
);
6321 for (Client::Type type
: AllClientTypes()) {
6322 (*mClients
)[type
]->OnOriginClearCompleted(aPersistenceType
, aOrigin
);
6325 (*mClients
)[aClientType
.Value()]->OnOriginClearCompleted(aPersistenceType
,
6330 Client
* QuotaManager::GetClient(Client::Type aClientType
) {
6331 MOZ_ASSERT(aClientType
>= Client::IDB
);
6332 MOZ_ASSERT(aClientType
< Client::TypeMax());
6334 return (*mClients
)[aClientType
];
6337 const AutoTArray
<Client::Type
, Client::TYPE_MAX
>&
6338 QuotaManager::AllClientTypes() {
6339 if (CachedNextGenLocalStorageEnabled()) {
6340 return *mAllClientTypes
;
6342 return *mAllClientTypesExceptLS
;
6345 uint64_t QuotaManager::GetGroupLimit() const {
6346 // To avoid one group evicting all the rest, limit the amount any one group
6347 // can use to 20% resp. a fifth. To prevent individual sites from using
6348 // exorbitant amounts of storage where there is a lot of free space, cap the
6349 // group limit to 2GB.
6350 const uint64_t x
= std::min
<uint64_t>(mTemporaryStorageLimit
/ 5, 2 GB
);
6352 // In low-storage situations, make an exception (while not exceeding the total
6354 return std::min
<uint64_t>(mTemporaryStorageLimit
,
6355 std::max
<uint64_t>(x
, 10 MB
));
6358 uint64_t QuotaManager::GetGroupUsage(const nsACString
& aGroup
) {
6359 AssertIsOnIOThread();
6364 MutexAutoLock
lock(mQuotaMutex
);
6366 GroupInfoPair
* pair
;
6367 if (mGroupInfoPairs
.Get(aGroup
, &pair
)) {
6368 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
6369 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(type
);
6371 AssertNoOverflow(usage
, groupInfo
->mUsage
);
6372 usage
+= groupInfo
->mUsage
;
6381 uint64_t QuotaManager::GetOriginUsage(
6382 const PrincipalMetadata
& aPrincipalMetadata
) {
6383 AssertIsOnIOThread();
6388 MutexAutoLock
lock(mQuotaMutex
);
6390 GroupInfoPair
* pair
;
6391 if (mGroupInfoPairs
.Get(aPrincipalMetadata
.mGroup
, &pair
)) {
6392 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
6393 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(type
);
6395 RefPtr
<OriginInfo
> originInfo
=
6396 groupInfo
->LockedGetOriginInfo(aPrincipalMetadata
.mOrigin
);
6398 AssertNoOverflow(usage
, originInfo
->LockedUsage());
6399 usage
+= originInfo
->LockedUsage();
6409 void QuotaManager::NotifyStoragePressure(uint64_t aUsage
) {
6410 mQuotaMutex
.AssertNotCurrentThreadOwns();
6412 RefPtr
<StoragePressureRunnable
> storagePressureRunnable
=
6413 new StoragePressureRunnable(aUsage
);
6415 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable
));
6419 void QuotaManager::GetStorageId(PersistenceType aPersistenceType
,
6420 const nsACString
& aOrigin
,
6421 Client::Type aClientType
,
6422 nsACString
& aDatabaseId
) {
6424 str
.AppendInt(aPersistenceType
);
6426 str
.Append(aOrigin
);
6428 str
.AppendInt(aClientType
);
6434 bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo
& aPrincipalInfo
) {
6435 switch (aPrincipalInfo
.type()) {
6436 // A system principal is acceptable.
6437 case PrincipalInfo::TSystemPrincipalInfo
: {
6441 // Validate content principals to ensure that the spec, originNoSuffix and
6442 // baseDomain are sane.
6443 case PrincipalInfo::TContentPrincipalInfo
: {
6444 const ContentPrincipalInfo
& info
=
6445 aPrincipalInfo
.get_ContentPrincipalInfo();
6447 // Verify the principal spec parses.
6448 RefPtr
<MozURL
> specURL
;
6449 nsresult rv
= MozURL::Init(getter_AddRefs(specURL
), info
.spec());
6450 if (NS_WARN_IF(NS_FAILED(rv
))) {
6451 QM_WARNING("A URL %s is not recognized by MozURL", info
.spec().get());
6455 // Verify the principal originNoSuffix matches spec.
6456 nsCString originNoSuffix
;
6457 specURL
->Origin(originNoSuffix
);
6459 if (NS_WARN_IF(originNoSuffix
!= info
.originNoSuffix())) {
6460 QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
6461 originNoSuffix
.get(), info
.originNoSuffix().get());
6465 if (NS_WARN_IF(info
.originNoSuffix().EqualsLiteral(kChromeOrigin
))) {
6469 if (NS_WARN_IF(info
.originNoSuffix().FindChar('^', 0) != -1)) {
6470 QM_WARNING("originNoSuffix (%s) contains the '^' character!",
6471 info
.originNoSuffix().get());
6475 // Verify the principal baseDomain exists.
6476 if (NS_WARN_IF(info
.baseDomain().IsVoid())) {
6480 // Verify the principal baseDomain matches spec.
6481 nsCString baseDomain
;
6482 rv
= specURL
->BaseDomain(baseDomain
);
6483 if (NS_WARN_IF(NS_FAILED(rv
))) {
6487 if (NS_WARN_IF(baseDomain
!= info
.baseDomain())) {
6488 QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
6489 baseDomain
.get(), info
.baseDomain().get());
6501 // Null and expanded principals are not acceptable.
6506 PrincipalMetadata
QuotaManager::GetInfoFromValidatedPrincipalInfo(
6507 const PrincipalInfo
& aPrincipalInfo
) {
6508 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo
));
6510 switch (aPrincipalInfo
.type()) {
6511 case PrincipalInfo::TSystemPrincipalInfo
: {
6512 return GetInfoForChrome();
6515 case PrincipalInfo::TContentPrincipalInfo
: {
6516 const ContentPrincipalInfo
& info
=
6517 aPrincipalInfo
.get_ContentPrincipalInfo();
6519 PrincipalMetadata principalMetadata
;
6521 info
.attrs().CreateSuffix(principalMetadata
.mSuffix
);
6523 principalMetadata
.mGroup
= info
.baseDomain() + principalMetadata
.mSuffix
;
6525 principalMetadata
.mOrigin
=
6526 info
.originNoSuffix() + principalMetadata
.mSuffix
;
6528 return principalMetadata
;
6532 MOZ_CRASH("Should never get here!");
6538 nsAutoCString
QuotaManager::GetOriginFromValidatedPrincipalInfo(
6539 const PrincipalInfo
& aPrincipalInfo
) {
6540 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo
));
6542 switch (aPrincipalInfo
.type()) {
6543 case PrincipalInfo::TSystemPrincipalInfo
: {
6544 return nsAutoCString
{GetOriginForChrome()};
6547 case PrincipalInfo::TContentPrincipalInfo
: {
6548 const ContentPrincipalInfo
& info
=
6549 aPrincipalInfo
.get_ContentPrincipalInfo();
6551 nsAutoCString suffix
;
6553 info
.attrs().CreateSuffix(suffix
);
6555 return info
.originNoSuffix() + suffix
;
6559 MOZ_CRASH("Should never get here!");
6565 Result
<PrincipalMetadata
, nsresult
> QuotaManager::GetInfoFromPrincipal(
6566 nsIPrincipal
* aPrincipal
) {
6567 MOZ_ASSERT(NS_IsMainThread());
6568 MOZ_ASSERT(aPrincipal
);
6570 if (aPrincipal
->IsSystemPrincipal()) {
6571 return GetInfoForChrome();
6574 if (aPrincipal
->GetIsNullPrincipal()) {
6575 NS_WARNING("IndexedDB not supported from this principal!");
6576 return Err(NS_ERROR_FAILURE
);
6579 PrincipalMetadata principalMetadata
;
6581 QM_TRY(aPrincipal
->GetOrigin(principalMetadata
.mOrigin
));
6583 if (principalMetadata
.mOrigin
.EqualsLiteral(kChromeOrigin
)) {
6584 NS_WARNING("Non-chrome principal can't use chrome origin!");
6585 return Err(NS_ERROR_FAILURE
);
6588 aPrincipal
->OriginAttributesRef().CreateSuffix(principalMetadata
.mSuffix
);
6590 nsAutoCString baseDomain
;
6591 QM_TRY(aPrincipal
->GetBaseDomain(baseDomain
));
6593 MOZ_ASSERT(!baseDomain
.IsEmpty());
6595 principalMetadata
.mGroup
= baseDomain
+ principalMetadata
.mSuffix
;
6597 return principalMetadata
;
6601 Result
<nsAutoCString
, nsresult
> QuotaManager::GetOriginFromPrincipal(
6602 nsIPrincipal
* aPrincipal
) {
6603 MOZ_ASSERT(NS_IsMainThread());
6604 MOZ_ASSERT(aPrincipal
);
6606 if (aPrincipal
->IsSystemPrincipal()) {
6607 return nsAutoCString
{GetOriginForChrome()};
6610 if (aPrincipal
->GetIsNullPrincipal()) {
6611 NS_WARNING("IndexedDB not supported from this principal!");
6612 return Err(NS_ERROR_FAILURE
);
6615 QM_TRY_UNWRAP(const auto origin
, MOZ_TO_RESULT_INVOKE_TYPED(
6616 nsAutoCString
, aPrincipal
, GetOrigin
));
6618 if (origin
.EqualsLiteral(kChromeOrigin
)) {
6619 NS_WARNING("Non-chrome principal can't use chrome origin!");
6620 return Err(NS_ERROR_FAILURE
);
6627 Result
<nsAutoCString
, nsresult
> QuotaManager::GetOriginFromWindow(
6628 nsPIDOMWindowOuter
* aWindow
) {
6629 MOZ_ASSERT(NS_IsMainThread());
6630 MOZ_ASSERT(aWindow
);
6632 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aWindow
);
6633 QM_TRY(OkIf(sop
), Err(NS_ERROR_FAILURE
));
6635 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
6636 QM_TRY(OkIf(principal
), Err(NS_ERROR_FAILURE
));
6638 QM_TRY_RETURN(GetOriginFromPrincipal(principal
));
6642 PrincipalMetadata
QuotaManager::GetInfoForChrome() {
6643 return {{}, GetOriginForChrome(), GetOriginForChrome()};
6647 nsLiteralCString
QuotaManager::GetOriginForChrome() {
6648 return nsLiteralCString
{kChromeOrigin
};
6652 bool QuotaManager::IsOriginInternal(const nsACString
& aOrigin
) {
6653 MOZ_ASSERT(!aOrigin
.IsEmpty());
6655 // The first prompt is not required for these origins.
6656 if (aOrigin
.EqualsLiteral(kChromeOrigin
) ||
6657 StringBeginsWith(aOrigin
, nsDependentCString(kAboutHomeOriginPrefix
)) ||
6658 StringBeginsWith(aOrigin
, nsDependentCString(kIndexedDBOriginPrefix
)) ||
6659 StringBeginsWith(aOrigin
, nsDependentCString(kResourceOriginPrefix
))) {
6667 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString
& aOrigin1
,
6668 const nsACString
& aOrigin2
) {
6669 return MakeSanitizedOriginCString(aOrigin1
) ==
6670 MakeSanitizedOriginCString(aOrigin2
);
6674 Result
<PrincipalInfo
, nsresult
> QuotaManager::ParseOrigin(
6675 const nsACString
& aOrigin
) {
6676 // An origin string either corresponds to a SystemPrincipalInfo or a
6677 // ContentPrincipalInfo, see
6678 // QuotaManager::GetOriginFromValidatedPrincipalInfo.
6680 if (aOrigin
.Equals(kChromeOrigin
)) {
6681 return PrincipalInfo
{SystemPrincipalInfo
{}};
6684 ContentPrincipalInfo contentPrincipalInfo
;
6686 nsCString originalSuffix
;
6687 const OriginParser::ResultType result
= OriginParser::ParseOrigin(
6688 MakeSanitizedOriginCString(aOrigin
), contentPrincipalInfo
.spec(),
6689 &contentPrincipalInfo
.attrs(), originalSuffix
);
6690 QM_TRY(OkIf(result
== OriginParser::ValidOrigin
), Err(NS_ERROR_FAILURE
));
6692 return PrincipalInfo
{std::move(contentPrincipalInfo
)};
6696 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache
= true; }
6698 uint64_t QuotaManager::LockedCollectOriginsForEviction(
6699 uint64_t aMinSizeToBeFreed
, nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
) {
6700 mQuotaMutex
.AssertCurrentThreadOwns();
6702 RefPtr
<CollectOriginsHelper
> helper
=
6703 new CollectOriginsHelper(mQuotaMutex
, aMinSizeToBeFreed
);
6705 // Unlock while calling out to XPCOM (code behind the dispatch method needs
6706 // to acquire its own lock which can potentially lead to a deadlock and it
6707 // also calls an observer that can do various stuff like IO, so it's better
6708 // to not hold our mutex while that happens).
6710 MutexAutoUnlock
autoUnlock(mQuotaMutex
);
6712 MOZ_ALWAYS_SUCCEEDS(mOwningThread
->Dispatch(helper
, NS_DISPATCH_NORMAL
));
6715 return helper
->BlockAndReturnOriginsForEviction(aLocks
);
6718 void QuotaManager::LockedRemoveQuotaForOrigin(
6719 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
) {
6720 mQuotaMutex
.AssertCurrentThreadOwns();
6721 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6723 GroupInfoPair
* pair
;
6724 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
6730 if (RefPtr
<GroupInfo
> groupInfo
=
6731 pair
->LockedGetGroupInfo(aPersistenceType
)) {
6732 groupInfo
->LockedRemoveOriginInfo(aOriginMetadata
.mOrigin
);
6734 if (!groupInfo
->LockedHasOriginInfos()) {
6735 pair
->LockedClearGroupInfo(aPersistenceType
);
6737 if (!pair
->LockedHasGroupInfos()) {
6738 mGroupInfoPairs
.Remove(aOriginMetadata
.mGroup
);
6744 already_AddRefed
<GroupInfo
> QuotaManager::LockedGetOrCreateGroupInfo(
6745 PersistenceType aPersistenceType
, const nsACString
& aSuffix
,
6746 const nsACString
& aGroup
) {
6747 mQuotaMutex
.AssertCurrentThreadOwns();
6748 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6750 GroupInfoPair
* const pair
=
6751 mGroupInfoPairs
.GetOrInsertNew(aGroup
, aSuffix
, aGroup
);
6753 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
6755 groupInfo
= new GroupInfo(pair
, aPersistenceType
);
6756 pair
->LockedSetGroupInfo(aPersistenceType
, groupInfo
);
6759 return groupInfo
.forget();
6762 already_AddRefed
<OriginInfo
> QuotaManager::LockedGetOriginInfo(
6763 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
) {
6764 mQuotaMutex
.AssertCurrentThreadOwns();
6765 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6767 GroupInfoPair
* pair
;
6768 if (mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
6769 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
6771 return groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
6778 template <typename Iterator
>
6779 void QuotaManager::MaybeInsertNonPersistedOriginInfos(
6780 Iterator aDest
, const RefPtr
<GroupInfo
>& aTemporaryGroupInfo
,
6781 const RefPtr
<GroupInfo
>& aDefaultGroupInfo
) {
6782 const auto copy
= [&aDest
](const GroupInfo
& groupInfo
) {
6784 groupInfo
.mOriginInfos
.cbegin(), groupInfo
.mOriginInfos
.cend(), aDest
,
6785 [](const auto& originInfo
) { return !originInfo
->LockedPersisted(); });
6788 if (aTemporaryGroupInfo
) {
6789 MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY
==
6790 aTemporaryGroupInfo
->GetPersistenceType());
6792 copy(*aTemporaryGroupInfo
);
6794 if (aDefaultGroupInfo
) {
6795 MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT
==
6796 aDefaultGroupInfo
->GetPersistenceType());
6798 copy(*aDefaultGroupInfo
);
6802 template <typename Collect
, typename Pred
>
6803 QuotaManager::OriginInfosFlatTraversable
6804 QuotaManager::CollectLRUOriginInfosUntil(Collect
&& aCollect
, Pred
&& aPred
) {
6805 OriginInfosFlatTraversable originInfos
;
6807 std::forward
<Collect
>(aCollect
)(MakeBackInserter(originInfos
));
6809 originInfos
.Sort(OriginInfoAccessTimeComparator());
6811 const auto foundIt
= std::find_if(originInfos
.cbegin(), originInfos
.cend(),
6812 std::forward
<Pred
>(aPred
));
6814 originInfos
.TruncateLength(foundIt
- originInfos
.cbegin());
6819 QuotaManager::OriginInfosNestedTraversable
6820 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
6821 MutexAutoLock
lock(mQuotaMutex
);
6823 OriginInfosNestedTraversable originInfos
;
6825 for (const auto& entry
: mGroupInfoPairs
) {
6826 const auto& pair
= entry
.GetData();
6828 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
6831 uint64_t groupUsage
= 0;
6833 const RefPtr
<GroupInfo
> temporaryGroupInfo
=
6834 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
);
6835 if (temporaryGroupInfo
) {
6836 groupUsage
+= temporaryGroupInfo
->mUsage
;
6839 const RefPtr
<GroupInfo
> defaultGroupInfo
=
6840 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
);
6841 if (defaultGroupInfo
) {
6842 groupUsage
+= defaultGroupInfo
->mUsage
;
6845 if (groupUsage
> 0) {
6846 QuotaManager
* quotaManager
= QuotaManager::Get();
6847 MOZ_ASSERT(quotaManager
, "Shouldn't be null!");
6849 if (groupUsage
> quotaManager
->GetGroupLimit()) {
6850 originInfos
.AppendElement(CollectLRUOriginInfosUntil(
6851 [&temporaryGroupInfo
, &defaultGroupInfo
](auto inserter
) {
6852 MaybeInsertNonPersistedOriginInfos(
6853 std::move(inserter
), temporaryGroupInfo
, defaultGroupInfo
);
6855 [&groupUsage
, quotaManager
](const auto& originInfo
) {
6856 groupUsage
-= originInfo
->LockedUsage();
6858 return groupUsage
<= quotaManager
->GetGroupLimit();
6867 QuotaManager::OriginInfosNestedTraversable
6868 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
6869 MutexAutoLock
lock(mQuotaMutex
);
6871 QuotaManager::OriginInfosNestedTraversable res
;
6872 res
.AppendElement(CollectLRUOriginInfosUntil(
6873 // XXX The lambda only needs to capture this, but due to Bug 1421435 it
6875 [&](auto inserter
) {
6876 for (const auto& entry
: mGroupInfoPairs
) {
6877 const auto& pair
= entry
.GetData();
6879 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
6882 MaybeInsertNonPersistedOriginInfos(
6883 inserter
, pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
),
6884 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
));
6887 [temporaryStorageUsage
= mTemporaryStorageUsage
,
6888 temporaryStorageLimit
= mTemporaryStorageLimit
,
6889 doomedUsage
= uint64_t{0}](const auto& originInfo
) mutable {
6890 if (temporaryStorageUsage
- doomedUsage
<= temporaryStorageLimit
) {
6894 doomedUsage
+= originInfo
->LockedUsage();
6901 void QuotaManager::ClearOrigins(
6902 const OriginInfosNestedTraversable
& aDoomedOriginInfos
) {
6903 AssertIsOnIOThread();
6905 // XXX Does this need to be done a) in order and/or b) sequentially?
6906 for (const auto& doomedOriginInfo
:
6907 Flatten
<OriginInfosFlatTraversable::elem_type
>(aDoomedOriginInfos
)) {
6910 MutexAutoLock
lock(mQuotaMutex
);
6911 MOZ_ASSERT(!doomedOriginInfo
->LockedPersisted());
6915 DeleteFilesForOrigin(doomedOriginInfo
->mGroupInfo
->mPersistenceType
,
6916 doomedOriginInfo
->mOrigin
);
6919 struct OriginParams
{
6921 PersistenceType mPersistenceType
;
6924 nsTArray
<OriginParams
> clearedOrigins
;
6927 MutexAutoLock
lock(mQuotaMutex
);
6929 for (const auto& doomedOriginInfo
:
6930 Flatten
<OriginInfosFlatTraversable::elem_type
>(aDoomedOriginInfos
)) {
6931 // LockedRemoveQuotaForOrigin might remove the group info;
6932 // OriginInfo::mGroupInfo is only a raw pointer, so we need to store the
6933 // information for calling OriginClearCompleted below in a separate array.
6934 clearedOrigins
.AppendElement(
6935 OriginParams
{doomedOriginInfo
->mOrigin
,
6936 doomedOriginInfo
->mGroupInfo
->mPersistenceType
});
6938 LockedRemoveQuotaForOrigin(doomedOriginInfo
->mGroupInfo
->mPersistenceType
,
6939 doomedOriginInfo
->FlattenToOriginMetadata());
6943 for (const auto& clearedOrigin
: clearedOrigins
) {
6944 OriginClearCompleted(clearedOrigin
.mPersistenceType
, clearedOrigin
.mOrigin
,
6945 Nullable
<Client::Type
>());
6949 void QuotaManager::CleanupTemporaryStorage() {
6950 AssertIsOnIOThread();
6952 // Evicting origins that exceed their group limit also affects the global
6953 // temporary storage usage, so these steps have to be taken sequentially.
6954 // Combining them doesn't seem worth the added complexity.
6955 ClearOrigins(GetOriginInfosExceedingGroupLimit());
6956 ClearOrigins(GetOriginInfosExceedingGlobalLimit());
6958 if (mTemporaryStorageUsage
> mTemporaryStorageLimit
) {
6959 // If disk space is still low after origin clear, notify storage pressure.
6960 NotifyStoragePressure(mTemporaryStorageUsage
);
6964 void QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType
,
6965 const nsACString
& aOrigin
) {
6966 QM_TRY_INSPECT(const auto& directory
,
6967 GetDirectoryForOrigin(aPersistenceType
, aOrigin
), QM_VOID
);
6969 nsresult rv
= directory
->Remove(true);
6970 if (rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
&&
6971 rv
!= NS_ERROR_FILE_NOT_FOUND
&& NS_FAILED(rv
)) {
6972 // This should never fail if we've closed all storage connections
6974 NS_ERROR("Failed to remove directory!");
6978 void QuotaManager::FinalizeOriginEviction(
6979 nsTArray
<RefPtr
<OriginDirectoryLock
>>&& aLocks
) {
6980 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
6982 RefPtr
<FinalizeOriginEvictionOp
> op
=
6983 new FinalizeOriginEvictionOp(mOwningThread
, std::move(aLocks
));
6985 if (IsOnIOThread()) {
6986 op
->RunOnIOThreadImmediately();
6992 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType
)
6993 -> DirectoryLockTable
& {
6994 switch (aPersistenceType
) {
6995 case PERSISTENCE_TYPE_TEMPORARY
:
6996 return mTemporaryDirectoryLockTable
;
6997 case PERSISTENCE_TYPE_DEFAULT
:
6998 return mDefaultDirectoryLockTable
;
7000 case PERSISTENCE_TYPE_PERSISTENT
:
7001 case PERSISTENCE_TYPE_INVALID
:
7003 MOZ_CRASH("Bad persistence type value!");
7007 bool QuotaManager::IsSanitizedOriginValid(const nsACString
& aSanitizedOrigin
) {
7008 AssertIsOnIOThread();
7010 // Do not parse this sanitized origin string, if we already parsed it.
7011 return mValidOrigins
.LookupOrInsertWith(
7012 aSanitizedOrigin
, [&aSanitizedOrigin
] {
7014 OriginAttributes attrs
;
7015 nsCString originalSuffix
;
7016 const auto result
= OriginParser::ParseOrigin(aSanitizedOrigin
, spec
,
7017 &attrs
, originalSuffix
);
7019 return result
== OriginParser::ValidOrigin
;
7023 int64_t QuotaManager::GenerateDirectoryLockId() {
7024 const int64_t directorylockId
= mNextDirectoryLockId
;
7026 if (CheckedInt64 result
= CheckedInt64(mNextDirectoryLockId
) + 1;
7028 mNextDirectoryLockId
= result
.value();
7030 NS_WARNING("Quota manager has run out of ids for directory locks!");
7032 // There's very little chance for this to happen given the max size of
7033 // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
7034 // to zero since such old directory locks shouldn't exist anymore.
7035 mNextDirectoryLockId
= 0;
7038 // TODO: Maybe add an assertion here to check that there is no existing
7039 // directory lock with given id.
7041 return directorylockId
;
7044 /*******************************************************************************
7045 * Local class implementations
7046 ******************************************************************************/
7048 void ClientUsageArray::Serialize(nsACString
& aText
) const {
7049 QuotaManager
* quotaManager
= QuotaManager::Get();
7050 MOZ_ASSERT(quotaManager
);
7054 for (Client::Type type
: quotaManager
->AllClientTypes()) {
7055 const Maybe
<uint64_t>& clientUsage
= ElementAt(type
);
7056 if (clientUsage
.isSome()) {
7063 aText
.Append(Client::TypeToPrefix(type
));
7064 aText
.AppendInt(clientUsage
.value());
7069 nsresult
ClientUsageArray::Deserialize(const nsACString
& aText
) {
7070 for (const auto& token
:
7071 nsCCharSeparatedTokenizerTemplate
<NS_TokenizerIgnoreNothing
>(aText
, ' ')
7073 QM_TRY(OkIf(token
.Length() >= 2), NS_ERROR_FAILURE
);
7075 Client::Type clientType
;
7076 QM_TRY(OkIf(Client::TypeFromPrefix(token
.First(), clientType
, fallible
)),
7080 const uint64_t usage
= Substring(token
, 1).ToInteger(&rv
);
7081 QM_TRY(ToResult(rv
));
7083 ElementAt(clientType
) = Some(usage
);
7089 OriginInfo::OriginInfo(GroupInfo
* aGroupInfo
, const nsACString
& aOrigin
,
7090 const ClientUsageArray
& aClientUsages
, uint64_t aUsage
,
7091 int64_t aAccessTime
, bool aPersisted
,
7092 bool aDirectoryExists
)
7093 : mClientUsages(aClientUsages
.Clone()),
7094 mGroupInfo(aGroupInfo
),
7097 mAccessTime(aAccessTime
),
7099 mPersisted(aPersisted
),
7100 mDirectoryExists(aDirectoryExists
) {
7101 MOZ_ASSERT(aGroupInfo
);
7102 MOZ_ASSERT(aClientUsages
.Length() == Client::TypeMax());
7103 MOZ_ASSERT_IF(aPersisted
,
7104 aGroupInfo
->mPersistenceType
== PERSISTENCE_TYPE_DEFAULT
);
7107 QuotaManager
* quotaManager
= QuotaManager::Get();
7108 MOZ_ASSERT(quotaManager
);
7111 for (Client::Type type
: quotaManager
->AllClientTypes()) {
7112 AssertNoOverflow(usage
, aClientUsages
[type
].valueOr(0));
7113 usage
+= aClientUsages
[type
].valueOr(0);
7115 MOZ_ASSERT(aUsage
== usage
);
7118 MOZ_COUNT_CTOR(OriginInfo
);
7121 OriginMetadata
OriginInfo::FlattenToOriginMetadata() const {
7122 return {mGroupInfo
->mGroupInfoPair
->Suffix(),
7123 mGroupInfo
->mGroupInfoPair
->Group(), mOrigin
,
7124 mGroupInfo
->mPersistenceType
};
7127 nsresult
OriginInfo::LockedBindToStatement(
7128 mozIStorageStatement
* aStatement
) const {
7129 AssertCurrentThreadOwnsQuotaMutex();
7130 MOZ_ASSERT(mGroupInfo
);
7132 QM_TRY(aStatement
->BindInt32ByName("repository_id"_ns
,
7133 mGroupInfo
->mPersistenceType
));
7135 QM_TRY(aStatement
->BindUTF8StringByName(
7136 "suffix"_ns
, mGroupInfo
->mGroupInfoPair
->Suffix()));
7137 QM_TRY(aStatement
->BindUTF8StringByName("group_"_ns
,
7138 mGroupInfo
->mGroupInfoPair
->Group()));
7139 QM_TRY(aStatement
->BindUTF8StringByName("origin"_ns
, mOrigin
));
7141 nsCString clientUsagesText
;
7142 mClientUsages
.Serialize(clientUsagesText
);
7145 aStatement
->BindUTF8StringByName("client_usages"_ns
, clientUsagesText
));
7146 QM_TRY(aStatement
->BindInt64ByName("usage"_ns
, mUsage
));
7147 QM_TRY(aStatement
->BindInt64ByName("last_access_time"_ns
, mAccessTime
));
7148 QM_TRY(aStatement
->BindInt32ByName("accessed"_ns
, mAccessed
));
7149 QM_TRY(aStatement
->BindInt32ByName("persisted"_ns
, mPersisted
));
7154 void OriginInfo::LockedDecreaseUsage(Client::Type aClientType
, int64_t aSize
) {
7155 AssertCurrentThreadOwnsQuotaMutex();
7157 MOZ_ASSERT(mClientUsages
[aClientType
].isSome());
7158 AssertNoUnderflow(mClientUsages
[aClientType
].value(), aSize
);
7159 mClientUsages
[aClientType
] = Some(mClientUsages
[aClientType
].value() - aSize
);
7161 AssertNoUnderflow(mUsage
, aSize
);
7164 if (!LockedPersisted()) {
7165 AssertNoUnderflow(mGroupInfo
->mUsage
, aSize
);
7166 mGroupInfo
->mUsage
-= aSize
;
7169 QuotaManager
* quotaManager
= QuotaManager::Get();
7170 MOZ_ASSERT(quotaManager
);
7172 AssertNoUnderflow(quotaManager
->mTemporaryStorageUsage
, aSize
);
7173 quotaManager
->mTemporaryStorageUsage
-= aSize
;
7176 void OriginInfo::LockedResetUsageForClient(Client::Type aClientType
) {
7177 AssertCurrentThreadOwnsQuotaMutex();
7179 uint64_t size
= mClientUsages
[aClientType
].valueOr(0);
7181 mClientUsages
[aClientType
].reset();
7183 AssertNoUnderflow(mUsage
, size
);
7186 if (!LockedPersisted()) {
7187 AssertNoUnderflow(mGroupInfo
->mUsage
, size
);
7188 mGroupInfo
->mUsage
-= size
;
7191 QuotaManager
* quotaManager
= QuotaManager::Get();
7192 MOZ_ASSERT(quotaManager
);
7194 AssertNoUnderflow(quotaManager
->mTemporaryStorageUsage
, size
);
7195 quotaManager
->mTemporaryStorageUsage
-= size
;
7198 UsageInfo
OriginInfo::LockedGetUsageForClient(Client::Type aClientType
) {
7199 AssertCurrentThreadOwnsQuotaMutex();
7201 // The current implementation of this method only supports DOMCACHE and LS,
7202 // which only use DatabaseUsage. If this assertion is lifted, the logic below
7204 MOZ_ASSERT(aClientType
== Client::Type::DOMCACHE
||
7205 aClientType
== Client::Type::LS
);
7207 return UsageInfo
{DatabaseUsageType
{mClientUsages
[aClientType
]}};
7210 void OriginInfo::LockedPersist() {
7211 AssertCurrentThreadOwnsQuotaMutex();
7212 MOZ_ASSERT(mGroupInfo
->mPersistenceType
== PERSISTENCE_TYPE_DEFAULT
);
7213 MOZ_ASSERT(!mPersisted
);
7217 // Remove Usage from GroupInfo
7218 AssertNoUnderflow(mGroupInfo
->mUsage
, mUsage
);
7219 mGroupInfo
->mUsage
-= mUsage
;
7222 already_AddRefed
<OriginInfo
> GroupInfo::LockedGetOriginInfo(
7223 const nsACString
& aOrigin
) {
7224 AssertCurrentThreadOwnsQuotaMutex();
7226 for (const auto& originInfo
: mOriginInfos
) {
7227 if (originInfo
->mOrigin
== aOrigin
) {
7228 RefPtr
<OriginInfo
> result
= originInfo
;
7229 return result
.forget();
7236 void GroupInfo::LockedAddOriginInfo(NotNull
<RefPtr
<OriginInfo
>>&& aOriginInfo
) {
7237 AssertCurrentThreadOwnsQuotaMutex();
7239 NS_ASSERTION(!mOriginInfos
.Contains(aOriginInfo
),
7240 "Replacing an existing entry!");
7241 mOriginInfos
.AppendElement(std::move(aOriginInfo
));
7243 uint64_t usage
= aOriginInfo
->LockedUsage();
7245 if (!aOriginInfo
->LockedPersisted()) {
7246 AssertNoOverflow(mUsage
, usage
);
7250 QuotaManager
* quotaManager
= QuotaManager::Get();
7251 MOZ_ASSERT(quotaManager
);
7253 AssertNoOverflow(quotaManager
->mTemporaryStorageUsage
, usage
);
7254 quotaManager
->mTemporaryStorageUsage
+= usage
;
7257 void GroupInfo::LockedAdjustUsageForRemovedOriginInfo(
7258 const OriginInfo
& aOriginInfo
) {
7259 const uint64_t usage
= aOriginInfo
.LockedUsage();
7261 if (!aOriginInfo
.LockedPersisted()) {
7262 AssertNoUnderflow(mUsage
, usage
);
7266 QuotaManager
* const quotaManager
= QuotaManager::Get();
7267 MOZ_ASSERT(quotaManager
);
7269 AssertNoUnderflow(quotaManager
->mTemporaryStorageUsage
, usage
);
7270 quotaManager
->mTemporaryStorageUsage
-= usage
;
7273 void GroupInfo::LockedRemoveOriginInfo(const nsACString
& aOrigin
) {
7274 AssertCurrentThreadOwnsQuotaMutex();
7276 const auto foundIt
= std::find_if(mOriginInfos
.cbegin(), mOriginInfos
.cend(),
7277 [&aOrigin
](const auto& originInfo
) {
7278 return originInfo
->mOrigin
== aOrigin
;
7281 // XXX Or can we MOZ_ASSERT(foundIt != mOriginInfos.cend()) ?
7282 if (foundIt
!= mOriginInfos
.cend()) {
7283 LockedAdjustUsageForRemovedOriginInfo(**foundIt
);
7285 mOriginInfos
.RemoveElementAt(foundIt
);
7289 void GroupInfo::LockedRemoveOriginInfos() {
7290 AssertCurrentThreadOwnsQuotaMutex();
7292 for (const auto& originInfo
: std::exchange(mOriginInfos
, {})) {
7293 LockedAdjustUsageForRemovedOriginInfo(*originInfo
);
7297 RefPtr
<GroupInfo
>& GroupInfoPair::GetGroupInfoForPersistenceType(
7298 PersistenceType aPersistenceType
) {
7299 switch (aPersistenceType
) {
7300 case PERSISTENCE_TYPE_TEMPORARY
:
7301 return mTemporaryStorageGroupInfo
;
7302 case PERSISTENCE_TYPE_DEFAULT
:
7303 return mDefaultStorageGroupInfo
;
7305 case PERSISTENCE_TYPE_PERSISTENT
:
7306 case PERSISTENCE_TYPE_INVALID
:
7308 MOZ_CRASH("Bad persistence type value!");
7312 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex
& aMutex
,
7313 uint64_t aMinSizeToBeFreed
)
7314 : Runnable("dom::quota::CollectOriginsHelper"),
7315 mMinSizeToBeFreed(aMinSizeToBeFreed
),
7317 mCondVar(aMutex
, "CollectOriginsHelper::mCondVar"),
7320 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7321 mMutex
.AssertCurrentThreadOwns();
7324 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
7325 nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
) {
7326 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7327 mMutex
.AssertCurrentThreadOwns();
7333 mLocks
.SwapElements(aLocks
);
7334 return mSizeToBeFreed
;
7338 CollectOriginsHelper::Run() {
7339 AssertIsOnBackgroundThread();
7341 QuotaManager
* quotaManager
= QuotaManager::Get();
7342 NS_ASSERTION(quotaManager
, "Shouldn't be null!");
7344 // We use extra stack vars here to avoid race detector warnings (the same
7345 // memory accessed with and without the lock held).
7346 nsTArray
<RefPtr
<OriginDirectoryLock
>> locks
;
7347 uint64_t sizeToBeFreed
=
7348 quotaManager
->CollectOriginsForEviction(mMinSizeToBeFreed
, locks
);
7350 MutexAutoLock
lock(mMutex
);
7352 NS_ASSERTION(mWaiting
, "Huh?!");
7354 mLocks
.SwapElements(locks
);
7355 mSizeToBeFreed
= sizeToBeFreed
;
7362 /*******************************************************************************
7363 * OriginOperationBase
7364 ******************************************************************************/
7367 OriginOperationBase::Run() {
7371 case State_Initial
: {
7376 case State_CreatingQuotaManager
: {
7377 rv
= QuotaManagerOpen();
7381 case State_DirectoryOpenPending
: {
7382 rv
= DirectoryOpen();
7386 case State_DirectoryWorkOpen
: {
7387 rv
= DirectoryWork();
7391 case State_UnblockingOpen
: {
7397 MOZ_CRASH("Bad state!");
7400 if (NS_WARN_IF(NS_FAILED(rv
)) && mState
!= State_UnblockingOpen
) {
7407 nsresult
OriginOperationBase::DirectoryOpen() {
7408 AssertIsOnOwningThread();
7409 MOZ_ASSERT(mState
== State_DirectoryOpenPending
);
7411 QuotaManager
* const quotaManager
= QuotaManager::Get();
7412 QM_TRY(OkIf(quotaManager
), NS_ERROR_FAILURE
);
7414 // Must set this before dispatching otherwise we will race with the IO thread.
7417 QM_TRY(quotaManager
->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL
),
7423 void OriginOperationBase::Finish(nsresult aResult
) {
7424 if (NS_SUCCEEDED(mResultCode
)) {
7425 mResultCode
= aResult
;
7428 // Must set mState before dispatching otherwise we will race with the main
7430 mState
= State_UnblockingOpen
;
7432 MOZ_ALWAYS_SUCCEEDS(mOwningThread
->Dispatch(this, NS_DISPATCH_NORMAL
));
7435 nsresult
OriginOperationBase::Init() {
7436 AssertIsOnOwningThread();
7437 MOZ_ASSERT(mState
== State_Initial
);
7439 if (QuotaManager::IsShuttingDown()) {
7440 return NS_ERROR_FAILURE
;
7445 if (mNeedsQuotaManagerInit
&& !QuotaManager::Get()) {
7446 QuotaManager::GetOrCreate(this);
7454 nsresult
OriginOperationBase::QuotaManagerOpen() {
7455 AssertIsOnOwningThread();
7456 MOZ_ASSERT(mState
== State_CreatingQuotaManager
);
7458 if (NS_WARN_IF(!QuotaManager::Get())) {
7459 return NS_ERROR_FAILURE
;
7467 nsresult
OriginOperationBase::DirectoryWork() {
7468 AssertIsOnIOThread();
7469 MOZ_ASSERT(mState
== State_DirectoryWorkOpen
);
7471 QuotaManager
* const quotaManager
= QuotaManager::Get();
7472 QM_TRY(OkIf(quotaManager
), NS_ERROR_FAILURE
);
7474 if (mNeedsStorageInit
) {
7475 QM_TRY(quotaManager
->EnsureStorageIsInitialized());
7478 QM_TRY(DoDirectoryWork(*quotaManager
));
7480 // Must set mState before dispatching otherwise we will race with the owning
7484 MOZ_ALWAYS_SUCCEEDS(mOwningThread
->Dispatch(this, NS_DISPATCH_NORMAL
));
7489 void FinalizeOriginEvictionOp::Dispatch() {
7490 MOZ_ASSERT(!NS_IsMainThread());
7491 MOZ_ASSERT(GetState() == State_Initial
);
7493 SetState(State_DirectoryOpenPending
);
7495 MOZ_ALWAYS_SUCCEEDS(mOwningThread
->Dispatch(this, NS_DISPATCH_NORMAL
));
7498 void FinalizeOriginEvictionOp::RunOnIOThreadImmediately() {
7499 AssertIsOnIOThread();
7500 MOZ_ASSERT(GetState() == State_Initial
);
7502 SetState(State_DirectoryWorkOpen
);
7504 MOZ_ALWAYS_SUCCEEDS(this->Run());
7507 void FinalizeOriginEvictionOp::Open() { MOZ_CRASH("Shouldn't get here!"); }
7509 nsresult
FinalizeOriginEvictionOp::DoDirectoryWork(
7510 QuotaManager
& aQuotaManager
) {
7511 AssertIsOnIOThread();
7513 AUTO_PROFILER_LABEL("FinalizeOriginEvictionOp::DoDirectoryWork", OTHER
);
7515 for (const auto& lock
: mLocks
) {
7516 aQuotaManager
.OriginClearCompleted(
7517 lock
->GetPersistenceType(), lock
->Origin(), Nullable
<Client::Type
>());
7523 void FinalizeOriginEvictionOp::UnblockOpen() {
7524 AssertIsOnOwningThread();
7525 MOZ_ASSERT(GetState() == State_UnblockingOpen
);
7528 NoteActorDestroyed();
7536 NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase
, Runnable
)
7538 void NormalOriginOperationBase::Open() {
7539 AssertIsOnOwningThread();
7540 MOZ_ASSERT(GetState() == State_CreatingQuotaManager
);
7541 MOZ_ASSERT(QuotaManager::Get());
7545 if (mNeedsDirectoryLocking
) {
7546 RefPtr
<DirectoryLock
> directoryLock
=
7547 QuotaManager::Get()->CreateDirectoryLockInternal(
7548 mPersistenceType
, mOriginScope
, mClientType
, mExclusive
);
7550 directoryLock
->Acquire(this);
7552 QM_TRY(DirectoryOpen(), QM_VOID
, [this](const nsresult rv
) { Finish(rv
); });
7556 void NormalOriginOperationBase::UnblockOpen() {
7557 AssertIsOnOwningThread();
7558 MOZ_ASSERT(GetState() == State_UnblockingOpen
);
7562 if (mNeedsDirectoryLocking
) {
7563 mDirectoryLock
= nullptr;
7566 UnregisterNormalOriginOp(*this);
7571 void NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock
* aLock
) {
7572 AssertIsOnOwningThread();
7574 MOZ_ASSERT(GetState() == State_DirectoryOpenPending
);
7575 MOZ_ASSERT(!mDirectoryLock
);
7577 mDirectoryLock
= aLock
;
7579 QM_TRY(DirectoryOpen(), QM_VOID
, [this](const nsresult rv
) { Finish(rv
); });
7582 void NormalOriginOperationBase::DirectoryLockFailed() {
7583 AssertIsOnOwningThread();
7584 MOZ_ASSERT(GetState() == State_DirectoryOpenPending
);
7585 MOZ_ASSERT(!mDirectoryLock
);
7587 Finish(NS_ERROR_FAILURE
);
7590 nsresult
SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
7591 AssertIsOnIOThread();
7592 MOZ_ASSERT(!mPersistenceType
.IsNull());
7593 MOZ_ASSERT(mOriginScope
.IsOrigin());
7595 AUTO_PROFILER_LABEL("SaveOriginAccessTimeOp::DoDirectoryWork", OTHER
);
7597 QM_TRY_INSPECT(const auto& file
,
7598 aQuotaManager
.GetDirectoryForOrigin(mPersistenceType
.Value(),
7599 mOriginScope
.GetOrigin()));
7601 // The origin directory might not exist
7602 // anymore, because it was deleted by a clear operation.
7603 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(file
, Exists
));
7606 QM_TRY(file
->Append(nsLiteralString(METADATA_V2_FILE_NAME
)));
7608 QM_TRY_INSPECT(const auto& stream
,
7609 GetBinaryOutputStream(*file
, FileFlag::Update
));
7612 QM_TRY(stream
->Write64(mTimestamp
));
7618 void SaveOriginAccessTimeOp::SendResults() {
7620 NoteActorDestroyed();
7625 StoragePressureRunnable::Run() {
7626 MOZ_ASSERT(NS_IsMainThread());
7628 nsCOMPtr
<nsIObserverService
> obsSvc
= mozilla::services::GetObserverService();
7629 if (NS_WARN_IF(!obsSvc
)) {
7630 return NS_ERROR_FAILURE
;
7633 nsCOMPtr
<nsISupportsPRUint64
> wrapper
=
7634 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID
);
7635 if (NS_WARN_IF(!wrapper
)) {
7636 return NS_ERROR_FAILURE
;
7639 wrapper
->SetData(mUsage
);
7641 obsSvc
->NotifyObservers(wrapper
, "QuotaManager::StoragePressure", u
"");
7646 void RecordQuotaInfoLoadTimeHelper::Start() {
7647 AssertIsOnIOThread();
7649 // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
7650 // gLastOSWake is set, then this time duration would still be recorded with
7651 // key "Normal". We are assumming this is rather rare to happen.
7652 mStartTime
.init(TimeStamp::Now());
7653 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
7656 void RecordQuotaInfoLoadTimeHelper::End() {
7657 AssertIsOnIOThread();
7659 mEndTime
.init(TimeStamp::Now());
7660 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
7664 RecordQuotaInfoLoadTimeHelper::Run() {
7665 MOZ_ASSERT(NS_IsMainThread());
7667 if (mInitializedTime
.isSome()) {
7668 // Keys for QM_QUOTA_INFO_LOAD_TIME_V0:
7669 // Normal: Normal conditions.
7670 // WasSuspended: There was a OS sleep so that it was suspended.
7671 // TimeStampErr1: The recorded start time is unexpectedly greater than the
7673 // TimeStampErr2: The initialized time for the recording class is unexpectly
7674 // greater than the last OS wake time.
7675 const auto key
= [this, wasSuspended
= gLastOSWake
> *mInitializedTime
]() {
7677 return "WasSuspended"_ns
;
7680 // XXX File a bug if we have data for this key.
7681 // We found negative values in our query in STMO for
7682 // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
7683 // because the documentation for TimeStamp::Now() says it returns a
7684 // monotonically increasing number.
7685 if (*mStartTime
> *mEndTime
) {
7686 return "TimeStampErr1"_ns
;
7689 if (*mInitializedTime
> gLastOSWake
) {
7690 return "TimeStampErr2"_ns
;
7696 Telemetry::AccumulateTimeDelta(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0
, key
,
7697 *mStartTime
, *mEndTime
);
7702 gLastOSWake
= TimeStamp::Now();
7703 mInitializedTime
.init(gLastOSWake
);
7708 /*******************************************************************************
7710 ******************************************************************************/
7714 : mActorDestroyed(false)
7719 Quota::~Quota() { MOZ_ASSERT(mActorDestroyed
); }
7721 void Quota::StartIdleMaintenance() {
7722 AssertIsOnBackgroundThread();
7723 MOZ_ASSERT(!QuotaManager::IsShuttingDown());
7725 QuotaManager
* const quotaManager
= QuotaManager::Get();
7726 QM_TRY(OkIf(quotaManager
), QM_VOID
);
7728 quotaManager
->StartIdleMaintenance();
7731 bool Quota::VerifyRequestParams(const UsageRequestParams
& aParams
) const {
7732 AssertIsOnBackgroundThread();
7733 MOZ_ASSERT(aParams
.type() != UsageRequestParams::T__None
);
7735 switch (aParams
.type()) {
7736 case UsageRequestParams::TAllUsageParams
:
7739 case UsageRequestParams::TOriginUsageParams
: {
7740 const OriginUsageParams
& params
= aParams
.get_OriginUsageParams();
7743 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7744 ASSERT_UNLESS_FUZZING();
7752 MOZ_CRASH("Should never get here!");
7758 bool Quota::VerifyRequestParams(const RequestParams
& aParams
) const {
7759 AssertIsOnBackgroundThread();
7760 MOZ_ASSERT(aParams
.type() != RequestParams::T__None
);
7762 switch (aParams
.type()) {
7763 case RequestParams::TStorageNameParams
:
7764 case RequestParams::TStorageInitializedParams
:
7765 case RequestParams::TTemporaryStorageInitializedParams
:
7766 case RequestParams::TInitParams
:
7767 case RequestParams::TInitTemporaryStorageParams
:
7770 case RequestParams::TInitializePersistentOriginParams
: {
7771 const InitializePersistentOriginParams
& params
=
7772 aParams
.get_InitializePersistentOriginParams();
7775 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7776 ASSERT_UNLESS_FUZZING();
7783 case RequestParams::TInitializeTemporaryOriginParams
: {
7784 const InitializeTemporaryOriginParams
& params
=
7785 aParams
.get_InitializeTemporaryOriginParams();
7787 if (NS_WARN_IF(!IsBestEffortPersistenceType(params
.persistenceType()))) {
7788 ASSERT_UNLESS_FUZZING();
7793 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7794 ASSERT_UNLESS_FUZZING();
7801 case RequestParams::TClearOriginParams
: {
7802 const ClearResetOriginParams
& params
=
7803 aParams
.get_ClearOriginParams().commonParams();
7806 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7807 ASSERT_UNLESS_FUZZING();
7811 if (params
.persistenceTypeIsExplicit()) {
7812 if (NS_WARN_IF(!IsValidPersistenceType(params
.persistenceType()))) {
7813 ASSERT_UNLESS_FUZZING();
7818 if (params
.clientTypeIsExplicit()) {
7819 if (NS_WARN_IF(!Client::IsValidType(params
.clientType()))) {
7820 ASSERT_UNLESS_FUZZING();
7828 case RequestParams::TResetOriginParams
: {
7829 const ClearResetOriginParams
& params
=
7830 aParams
.get_ResetOriginParams().commonParams();
7833 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7834 ASSERT_UNLESS_FUZZING();
7838 if (params
.persistenceTypeIsExplicit()) {
7839 if (NS_WARN_IF(!IsValidPersistenceType(params
.persistenceType()))) {
7840 ASSERT_UNLESS_FUZZING();
7845 if (params
.clientTypeIsExplicit()) {
7846 if (NS_WARN_IF(!Client::IsValidType(params
.clientType()))) {
7847 ASSERT_UNLESS_FUZZING();
7855 case RequestParams::TClearDataParams
: {
7856 if (BackgroundParent::IsOtherProcessActor(Manager())) {
7857 ASSERT_UNLESS_FUZZING();
7864 case RequestParams::TClearAllParams
:
7865 case RequestParams::TResetAllParams
:
7866 case RequestParams::TListOriginsParams
:
7869 case RequestParams::TPersistedParams
: {
7870 const PersistedParams
& params
= aParams
.get_PersistedParams();
7873 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7874 ASSERT_UNLESS_FUZZING();
7881 case RequestParams::TPersistParams
: {
7882 const PersistParams
& params
= aParams
.get_PersistParams();
7885 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7886 ASSERT_UNLESS_FUZZING();
7893 case RequestParams::TEstimateParams
: {
7894 const EstimateParams
& params
= aParams
.get_EstimateParams();
7897 !QuotaManager::IsPrincipalInfoValid(params
.principalInfo()))) {
7898 ASSERT_UNLESS_FUZZING();
7906 MOZ_CRASH("Should never get here!");
7912 void Quota::ActorDestroy(ActorDestroyReason aWhy
) {
7913 AssertIsOnBackgroundThread();
7915 MOZ_ASSERT(!mActorDestroyed
);
7916 mActorDestroyed
= true;
7920 PQuotaUsageRequestParent
* Quota::AllocPQuotaUsageRequestParent(
7921 const UsageRequestParams
& aParams
) {
7922 AssertIsOnBackgroundThread();
7923 MOZ_ASSERT(aParams
.type() != UsageRequestParams::T__None
);
7925 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
7930 // Always verify parameters in DEBUG builds!
7931 bool trustParams
= false;
7933 bool trustParams
= !BackgroundParent::IsOtherProcessActor(Manager());
7936 if (!trustParams
&& NS_WARN_IF(!VerifyRequestParams(aParams
))) {
7937 ASSERT_UNLESS_FUZZING();
7941 auto actor
= [&]() -> RefPtr
<QuotaUsageRequestBase
> {
7942 switch (aParams
.type()) {
7943 case UsageRequestParams::TAllUsageParams
:
7944 return MakeRefPtr
<GetUsageOp
>(aParams
);
7946 case UsageRequestParams::TOriginUsageParams
:
7947 return MakeRefPtr
<GetOriginUsageOp
>(aParams
);
7950 MOZ_CRASH("Should never get here!");
7956 RegisterNormalOriginOp(*actor
);
7958 // Transfer ownership to IPDL.
7959 return actor
.forget().take();
7962 mozilla::ipc::IPCResult
Quota::RecvPQuotaUsageRequestConstructor(
7963 PQuotaUsageRequestParent
* aActor
, const UsageRequestParams
& aParams
) {
7964 AssertIsOnBackgroundThread();
7966 MOZ_ASSERT(aParams
.type() != UsageRequestParams::T__None
);
7967 MOZ_ASSERT(!QuotaManager::IsShuttingDown());
7969 auto* op
= static_cast<QuotaUsageRequestBase
*>(aActor
);
7973 op
->RunImmediately();
7977 bool Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent
* aActor
) {
7978 AssertIsOnBackgroundThread();
7981 // Transfer ownership back from IPDL.
7982 RefPtr
<QuotaUsageRequestBase
> actor
=
7983 dont_AddRef(static_cast<QuotaUsageRequestBase
*>(aActor
));
7987 PQuotaRequestParent
* Quota::AllocPQuotaRequestParent(
7988 const RequestParams
& aParams
) {
7989 AssertIsOnBackgroundThread();
7990 MOZ_ASSERT(aParams
.type() != RequestParams::T__None
);
7992 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
7997 // Always verify parameters in DEBUG builds!
7998 bool trustParams
= false;
8000 bool trustParams
= !BackgroundParent::IsOtherProcessActor(Manager());
8003 if (!trustParams
&& NS_WARN_IF(!VerifyRequestParams(aParams
))) {
8004 ASSERT_UNLESS_FUZZING();
8008 auto actor
= [&]() -> RefPtr
<QuotaRequestBase
> {
8009 switch (aParams
.type()) {
8010 case RequestParams::TStorageNameParams
:
8011 return MakeRefPtr
<StorageNameOp
>();
8013 case RequestParams::TStorageInitializedParams
:
8014 return MakeRefPtr
<StorageInitializedOp
>();
8016 case RequestParams::TTemporaryStorageInitializedParams
:
8017 return MakeRefPtr
<TemporaryStorageInitializedOp
>();
8019 case RequestParams::TInitParams
:
8020 return MakeRefPtr
<InitOp
>();
8022 case RequestParams::TInitTemporaryStorageParams
:
8023 return MakeRefPtr
<InitTemporaryStorageOp
>();
8025 case RequestParams::TInitializePersistentOriginParams
:
8026 return MakeRefPtr
<InitializePersistentOriginOp
>(aParams
);
8028 case RequestParams::TInitializeTemporaryOriginParams
:
8029 return MakeRefPtr
<InitializeTemporaryOriginOp
>(aParams
);
8031 case RequestParams::TClearOriginParams
:
8032 return MakeRefPtr
<ClearOriginOp
>(aParams
);
8034 case RequestParams::TResetOriginParams
:
8035 return MakeRefPtr
<ResetOriginOp
>(aParams
);
8037 case RequestParams::TClearDataParams
:
8038 return MakeRefPtr
<ClearDataOp
>(aParams
);
8040 case RequestParams::TClearAllParams
:
8041 return MakeRefPtr
<ResetOrClearOp
>(/* aClear */ true);
8043 case RequestParams::TResetAllParams
:
8044 return MakeRefPtr
<ResetOrClearOp
>(/* aClear */ false);
8046 case RequestParams::TPersistedParams
:
8047 return MakeRefPtr
<PersistedOp
>(aParams
);
8049 case RequestParams::TPersistParams
:
8050 return MakeRefPtr
<PersistOp
>(aParams
);
8052 case RequestParams::TEstimateParams
:
8053 return MakeRefPtr
<EstimateOp
>(aParams
);
8055 case RequestParams::TListOriginsParams
:
8056 return MakeRefPtr
<ListOriginsOp
>();
8059 MOZ_CRASH("Should never get here!");
8065 RegisterNormalOriginOp(*actor
);
8067 // Transfer ownership to IPDL.
8068 return actor
.forget().take();
8071 mozilla::ipc::IPCResult
Quota::RecvPQuotaRequestConstructor(
8072 PQuotaRequestParent
* aActor
, const RequestParams
& aParams
) {
8073 AssertIsOnBackgroundThread();
8075 MOZ_ASSERT(aParams
.type() != RequestParams::T__None
);
8076 MOZ_ASSERT(!QuotaManager::IsShuttingDown());
8078 auto* op
= static_cast<QuotaRequestBase
*>(aActor
);
8082 op
->RunImmediately();
8086 bool Quota::DeallocPQuotaRequestParent(PQuotaRequestParent
* aActor
) {
8087 AssertIsOnBackgroundThread();
8090 // Transfer ownership back from IPDL.
8091 RefPtr
<QuotaRequestBase
> actor
=
8092 dont_AddRef(static_cast<QuotaRequestBase
*>(aActor
));
8096 mozilla::ipc::IPCResult
Quota::RecvStartIdleMaintenance() {
8097 AssertIsOnBackgroundThread();
8099 PBackgroundParent
* actor
= Manager();
8102 if (BackgroundParent::IsOtherProcessActor(actor
)) {
8103 ASSERT_UNLESS_FUZZING();
8104 return IPC_FAIL_NO_REASON(this);
8107 if (QuotaManager::IsShuttingDown()) {
8111 QuotaManager
* quotaManager
= QuotaManager::Get();
8112 if (!quotaManager
) {
8113 nsCOMPtr
<nsIRunnable
> callback
=
8114 NewRunnableMethod("dom::quota::Quota::StartIdleMaintenance", this,
8115 &Quota::StartIdleMaintenance
);
8117 QuotaManager::GetOrCreate(callback
);
8121 quotaManager
->StartIdleMaintenance();
8126 mozilla::ipc::IPCResult
Quota::RecvStopIdleMaintenance() {
8127 AssertIsOnBackgroundThread();
8129 PBackgroundParent
* actor
= Manager();
8132 if (BackgroundParent::IsOtherProcessActor(actor
)) {
8133 ASSERT_UNLESS_FUZZING();
8134 return IPC_FAIL_NO_REASON(this);
8137 if (QuotaManager::IsShuttingDown()) {
8141 QuotaManager
* quotaManager
= QuotaManager::Get();
8142 if (!quotaManager
) {
8146 quotaManager
->StopIdleMaintenance();
8151 mozilla::ipc::IPCResult
Quota::RecvAbortOperationsForProcess(
8152 const ContentParentId
& aContentParentId
) {
8153 AssertIsOnBackgroundThread();
8155 PBackgroundParent
* actor
= Manager();
8158 if (BackgroundParent::IsOtherProcessActor(actor
)) {
8159 ASSERT_UNLESS_FUZZING();
8160 return IPC_FAIL_NO_REASON(this);
8163 if (QuotaManager::IsShuttingDown()) {
8167 QuotaManager
* quotaManager
= QuotaManager::Get();
8168 if (!quotaManager
) {
8172 quotaManager
->AbortOperationsForProcess(aContentParentId
);
8177 void QuotaUsageRequestBase::Init(Quota
& aQuota
) {
8178 AssertIsOnOwningThread();
8180 mNeedsQuotaManagerInit
= true;
8181 mNeedsStorageInit
= true;
8184 Result
<UsageInfo
, nsresult
> QuotaUsageRequestBase::GetUsageForOrigin(
8185 QuotaManager
& aQuotaManager
, PersistenceType aPersistenceType
,
8186 const OriginMetadata
& aOriginMetadata
) {
8187 AssertIsOnIOThread();
8189 QM_TRY_INSPECT(const auto& directory
,
8190 aQuotaManager
.GetDirectoryForOrigin(aPersistenceType
,
8191 aOriginMetadata
.mOrigin
));
8193 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(directory
, Exists
));
8195 if (!exists
|| mCanceled
) {
8199 // If the directory exists then enumerate all the files inside, adding up
8200 // the sizes to get the final usage statistic.
8203 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
8204 initialized
= aQuotaManager
.IsOriginInitialized(aOriginMetadata
.mOrigin
);
8206 initialized
= aQuotaManager
.IsTemporaryStorageInitialized();
8209 return GetUsageForOriginEntries(aQuotaManager
, aPersistenceType
,
8210 aOriginMetadata
, *directory
, initialized
);
8213 Result
<UsageInfo
, nsresult
> QuotaUsageRequestBase::GetUsageForOriginEntries(
8214 QuotaManager
& aQuotaManager
, PersistenceType aPersistenceType
,
8215 const OriginMetadata
& aOriginMetadata
, nsIFile
& aDirectory
,
8216 const bool aInitialized
) {
8217 AssertIsOnIOThread();
8219 QM_TRY_RETURN((ReduceEachFileAtomicCancelable(
8220 aDirectory
, mCanceled
, UsageInfo
{},
8221 [&](UsageInfo oldUsageInfo
, const nsCOMPtr
<nsIFile
>& file
)
8222 -> mozilla::Result
<UsageInfo
, nsresult
> {
8224 const auto& leafName
,
8225 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, file
, GetLeafName
));
8227 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*file
));
8229 switch (dirEntryKind
) {
8230 case nsIFileKind::ExistsAsDirectory
: {
8231 Client::Type clientType
;
8233 Client::TypeFromText(leafName
, clientType
, fallible
);
8235 // Unknown directories during getting usage for an origin (even
8236 // for an uninitialized origin) are now allowed. Just warn if we
8238 UNKNOWN_FILE_WARNING(leafName
);
8242 Client
* const client
= aQuotaManager
.GetClient(clientType
);
8246 const auto& usageInfo
,
8247 aInitialized
? client
->GetUsageForOrigin(
8248 aPersistenceType
, aOriginMetadata
, mCanceled
)
8249 : client
->InitOrigin(aPersistenceType
,
8250 aOriginMetadata
, mCanceled
));
8251 return oldUsageInfo
+ usageInfo
;
8254 case nsIFileKind::ExistsAsFile
:
8255 // We are maintaining existing behavior for unknown files here (just
8257 // This can possibly be used by developers to add temporary backups
8258 // into origin directories without losing get usage functionality.
8259 if (IsTempMetadata(leafName
)) {
8260 if (!aInitialized
) {
8261 QM_TRY(file
->Remove(/* recursive */ false));
8267 if (IsOriginMetadata(leafName
) || IsOSMetadata(leafName
) ||
8268 IsDotFile(leafName
)) {
8272 // Unknown files during getting usage for an origin (even for an
8273 // uninitialized origin) are now allowed. Just warn if we find them.
8274 UNKNOWN_FILE_WARNING(leafName
);
8277 case nsIFileKind::DoesNotExist
:
8278 // Ignore files that got removed externally while iterating.
8282 return oldUsageInfo
;
8286 void QuotaUsageRequestBase::SendResults() {
8287 AssertIsOnOwningThread();
8289 if (IsActorDestroyed()) {
8290 if (NS_SUCCEEDED(mResultCode
)) {
8291 mResultCode
= NS_ERROR_FAILURE
;
8295 mResultCode
= NS_ERROR_FAILURE
;
8298 UsageRequestResponse response
;
8300 if (NS_SUCCEEDED(mResultCode
)) {
8301 GetResponse(response
);
8303 response
= mResultCode
;
8306 Unused
<< PQuotaUsageRequestParent::Send__delete__(this, response
);
8310 void QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy
) {
8311 AssertIsOnOwningThread();
8313 NoteActorDestroyed();
8316 mozilla::ipc::IPCResult
QuotaUsageRequestBase::RecvCancel() {
8317 AssertIsOnOwningThread();
8319 if (mCanceled
.exchange(true)) {
8320 NS_WARNING("Canceled more than once?!");
8321 return IPC_FAIL_NO_REASON(this);
8327 nsresult
TraverseRepositoryHelper::TraverseRepository(
8328 QuotaManager
& aQuotaManager
, PersistenceType aPersistenceType
) {
8329 AssertIsOnIOThread();
8332 const auto& directory
,
8333 QM_NewLocalFile(aQuotaManager
.GetStoragePath(aPersistenceType
)));
8335 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(directory
, Exists
));
8341 QM_TRY(CollectEachFileAtomicCancelable(
8342 *directory
, GetIsCanceledFlag(),
8343 [this, aPersistenceType
, &aQuotaManager
,
8344 persistent
= aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
](
8345 const nsCOMPtr
<nsIFile
>& originDir
) -> Result
<Ok
, nsresult
> {
8346 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*originDir
));
8348 switch (dirEntryKind
) {
8349 case nsIFileKind::ExistsAsDirectory
:
8350 QM_TRY(ProcessOrigin(aQuotaManager
, *originDir
, persistent
,
8354 case nsIFileKind::ExistsAsFile
: {
8355 QM_TRY_INSPECT(const auto& leafName
,
8356 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, originDir
,
8359 // Unknown files during getting usages are allowed. Just warn if we
8361 if (!IsOSMetadata(leafName
)) {
8362 UNKNOWN_FILE_WARNING(leafName
);
8368 case nsIFileKind::DoesNotExist
:
8369 // Ignore files that got removed externally while iterating.
8379 GetUsageOp::GetUsageOp(const UsageRequestParams
& aParams
)
8380 : mGetAll(aParams
.get_AllUsageParams().getAll()) {
8381 AssertIsOnOwningThread();
8382 MOZ_ASSERT(aParams
.type() == UsageRequestParams::TAllUsageParams
);
8385 void GetUsageOp::ProcessOriginInternal(QuotaManager
* aQuotaManager
,
8386 const PersistenceType aPersistenceType
,
8387 const nsACString
& aOrigin
,
8388 const int64_t aTimestamp
,
8389 const bool aPersisted
,
8390 const uint64_t aUsage
) {
8391 if (!mGetAll
&& aQuotaManager
->IsOriginInternal(aOrigin
)) {
8395 // We can't store pointers to OriginUsage objects in the hashtable
8396 // since AppendElement() reallocates its internal array buffer as number
8397 // of elements grows.
8398 const auto& originUsage
=
8399 mOriginUsagesIndex
.WithEntryHandle(aOrigin
, [&](auto&& entry
) {
8401 return WrapNotNullUnchecked(&mOriginUsages
[entry
.Data()]);
8404 entry
.Insert(mOriginUsages
.Length());
8406 return mOriginUsages
.EmplaceBack(nsCString
{aOrigin
}, false, 0, 0);
8409 if (aPersistenceType
== PERSISTENCE_TYPE_DEFAULT
) {
8410 originUsage
->persisted() = aPersisted
;
8413 originUsage
->usage() = originUsage
->usage() + aUsage
;
8415 originUsage
->lastAccessed() =
8416 std::max
<int64_t>(originUsage
->lastAccessed(), aTimestamp
);
8419 const Atomic
<bool>& GetUsageOp::GetIsCanceledFlag() {
8420 AssertIsOnIOThread();
8425 // XXX Remove aPersistent
8426 // XXX Remove aPersistenceType once GetUsageForOrigin uses the persistence
8427 // type from OriginMetadata
8428 nsresult
GetUsageOp::ProcessOrigin(QuotaManager
& aQuotaManager
,
8429 nsIFile
& aOriginDir
, const bool aPersistent
,
8430 const PersistenceType aPersistenceType
) {
8431 AssertIsOnIOThread();
8433 QM_TRY_INSPECT(const auto& metadata
,
8434 aQuotaManager
.LoadFullOriginMetadataWithRestore(&aOriginDir
));
8436 QM_TRY_INSPECT(const auto& usageInfo
,
8437 GetUsageForOrigin(aQuotaManager
, aPersistenceType
, metadata
));
8439 ProcessOriginInternal(&aQuotaManager
, aPersistenceType
, metadata
.mOrigin
,
8440 metadata
.mLastAccessTime
, metadata
.mPersisted
,
8441 usageInfo
.TotalUsage().valueOr(0));
8446 nsresult
GetUsageOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
8447 AssertIsOnIOThread();
8448 aQuotaManager
.AssertStorageIsInitialized();
8450 AUTO_PROFILER_LABEL("GetUsageOp::DoDirectoryWork", OTHER
);
8454 for (const PersistenceType type
: kAllPersistenceTypes
) {
8455 rv
= TraverseRepository(aQuotaManager
, type
);
8456 if (NS_WARN_IF(NS_FAILED(rv
))) {
8461 // TraverseRepository above only consulted the filesystem. We also need to
8462 // consider origins which may have pending quota usage, such as buffered
8463 // LocalStorage writes for an origin which didn't previously have any
8464 // LocalStorage data.
8466 aQuotaManager
.CollectPendingOriginsForListing(
8467 [this, &aQuotaManager
](const auto& originInfo
) {
8468 ProcessOriginInternal(
8469 &aQuotaManager
, originInfo
->GetGroupInfo()->GetPersistenceType(),
8470 originInfo
->Origin(), originInfo
->LockedAccessTime(),
8471 originInfo
->LockedPersisted(), originInfo
->LockedUsage());
8477 void GetUsageOp::GetResponse(UsageRequestResponse
& aResponse
) {
8478 AssertIsOnOwningThread();
8480 aResponse
= AllUsageResponse();
8482 aResponse
.get_AllUsageResponse().originUsages() = std::move(mOriginUsages
);
8485 GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams
& aParams
)
8486 : mUsage(0), mFileUsage(0) {
8487 AssertIsOnOwningThread();
8488 MOZ_ASSERT(aParams
.type() == UsageRequestParams::TOriginUsageParams
);
8490 const OriginUsageParams
& params
= aParams
.get_OriginUsageParams();
8492 PrincipalMetadata principalMetadata
=
8493 QuotaManager::GetInfoFromValidatedPrincipalInfo(params
.principalInfo());
8495 mSuffix
= std::move(principalMetadata
.mSuffix
);
8496 mGroup
= std::move(principalMetadata
.mGroup
);
8497 mOriginScope
.SetFromOrigin(principalMetadata
.mOrigin
);
8499 mFromMemory
= params
.fromMemory();
8501 // Overwrite NormalOriginOperationBase default values.
8503 mNeedsDirectoryLocking
= false;
8506 // Overwrite OriginOperationBase default values.
8507 mNeedsQuotaManagerInit
= true;
8508 mNeedsStorageInit
= true;
8511 nsresult
GetOriginUsageOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
8512 AssertIsOnIOThread();
8513 aQuotaManager
.AssertStorageIsInitialized();
8514 MOZ_ASSERT(mUsage
== 0);
8515 MOZ_ASSERT(mFileUsage
== 0);
8517 AUTO_PROFILER_LABEL("GetOriginUsageOp::DoDirectoryWork", OTHER
);
8520 const PrincipalMetadata principalMetadata
= {
8521 mSuffix
, mGroup
, nsCString
{mOriginScope
.GetOrigin()}};
8523 // Ensure temporary storage is initialized. If temporary storage hasn't been
8524 // initialized yet, the method will initialize it by traversing the
8525 // repositories for temporary and default storage (including our origin).
8526 QM_TRY(aQuotaManager
.EnsureTemporaryStorageIsInitialized());
8528 // Get cached usage (the method doesn't have to stat any files). File usage
8529 // is not tracked in memory separately, so just add to the total usage.
8530 mUsage
= aQuotaManager
.GetOriginUsage(principalMetadata
);
8535 UsageInfo usageInfo
;
8537 // Add all the persistent/temporary/default storage files we care about.
8538 for (const PersistenceType type
: kAllPersistenceTypes
) {
8539 const OriginMetadata originMetadata
= {
8540 mSuffix
, mGroup
, nsCString
{mOriginScope
.GetOrigin()}, type
};
8542 auto usageInfoOrErr
=
8543 GetUsageForOrigin(aQuotaManager
, type
, originMetadata
);
8544 if (NS_WARN_IF(usageInfoOrErr
.isErr())) {
8545 return usageInfoOrErr
.unwrapErr();
8548 usageInfo
+= usageInfoOrErr
.unwrap();
8551 mUsage
= usageInfo
.TotalUsage().valueOr(0);
8552 mFileUsage
= usageInfo
.FileUsage().valueOr(0);
8557 void GetOriginUsageOp::GetResponse(UsageRequestResponse
& aResponse
) {
8558 AssertIsOnOwningThread();
8560 OriginUsageResponse usageResponse
;
8562 usageResponse
.usage() = mUsage
;
8563 usageResponse
.fileUsage() = mFileUsage
;
8565 aResponse
= usageResponse
;
8568 void QuotaRequestBase::Init(Quota
& aQuota
) {
8569 AssertIsOnOwningThread();
8571 mNeedsQuotaManagerInit
= true;
8572 mNeedsStorageInit
= true;
8575 void QuotaRequestBase::SendResults() {
8576 AssertIsOnOwningThread();
8578 if (IsActorDestroyed()) {
8579 if (NS_SUCCEEDED(mResultCode
)) {
8580 mResultCode
= NS_ERROR_FAILURE
;
8583 RequestResponse response
;
8585 if (NS_SUCCEEDED(mResultCode
)) {
8586 GetResponse(response
);
8588 response
= mResultCode
;
8591 Unused
<< PQuotaRequestParent::Send__delete__(this, response
);
8595 void QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy
) {
8596 AssertIsOnOwningThread();
8598 NoteActorDestroyed();
8601 StorageNameOp::StorageNameOp() : QuotaRequestBase(/* aExclusive */ false) {
8602 AssertIsOnOwningThread();
8604 // Overwrite NormalOriginOperationBase default values.
8605 mNeedsDirectoryLocking
= false;
8607 // Overwrite OriginOperationBase default values.
8608 mNeedsQuotaManagerInit
= true;
8609 mNeedsStorageInit
= false;
8612 void StorageNameOp::Init(Quota
& aQuota
) { AssertIsOnOwningThread(); }
8614 nsresult
StorageNameOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
8615 AssertIsOnIOThread();
8617 AUTO_PROFILER_LABEL("StorageNameOp::DoDirectoryWork", OTHER
);
8619 mName
= aQuotaManager
.GetStorageName();
8624 void StorageNameOp::GetResponse(RequestResponse
& aResponse
) {
8625 AssertIsOnOwningThread();
8627 StorageNameResponse storageNameResponse
;
8629 storageNameResponse
.name() = mName
;
8631 aResponse
= storageNameResponse
;
8634 InitializedRequestBase::InitializedRequestBase()
8635 : QuotaRequestBase(/* aExclusive */ false), mInitialized(false) {
8636 AssertIsOnOwningThread();
8638 // Overwrite NormalOriginOperationBase default values.
8639 mNeedsDirectoryLocking
= false;
8641 // Overwrite OriginOperationBase default values.
8642 mNeedsQuotaManagerInit
= true;
8643 mNeedsStorageInit
= false;
8646 void InitializedRequestBase::Init(Quota
& aQuota
) { AssertIsOnOwningThread(); }
8648 nsresult
StorageInitializedOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
8649 AssertIsOnIOThread();
8651 AUTO_PROFILER_LABEL("StorageInitializedOp::DoDirectoryWork", OTHER
);
8653 mInitialized
= aQuotaManager
.IsStorageInitialized();
8658 void StorageInitializedOp::GetResponse(RequestResponse
& aResponse
) {
8659 AssertIsOnOwningThread();
8661 StorageInitializedResponse storageInitializedResponse
;
8663 storageInitializedResponse
.initialized() = mInitialized
;
8665 aResponse
= storageInitializedResponse
;
8668 nsresult
TemporaryStorageInitializedOp::DoDirectoryWork(
8669 QuotaManager
& aQuotaManager
) {
8670 AssertIsOnIOThread();
8672 AUTO_PROFILER_LABEL("TemporaryStorageInitializedOp::DoDirectoryWork", OTHER
);
8674 mInitialized
= aQuotaManager
.IsTemporaryStorageInitialized();
8679 void TemporaryStorageInitializedOp::GetResponse(RequestResponse
& aResponse
) {
8680 AssertIsOnOwningThread();
8682 TemporaryStorageInitializedResponse temporaryStorageInitializedResponse
;
8684 temporaryStorageInitializedResponse
.initialized() = mInitialized
;
8686 aResponse
= temporaryStorageInitializedResponse
;
8689 InitOp::InitOp() : QuotaRequestBase(/* aExclusive */ false) {
8690 AssertIsOnOwningThread();
8692 // Overwrite OriginOperationBase default values.
8693 mNeedsQuotaManagerInit
= true;
8694 mNeedsStorageInit
= false;
8697 void InitOp::Init(Quota
& aQuota
) { AssertIsOnOwningThread(); }
8699 nsresult
InitOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
8700 AssertIsOnIOThread();
8702 AUTO_PROFILER_LABEL("InitOp::DoDirectoryWork", OTHER
);
8704 QM_TRY(aQuotaManager
.EnsureStorageIsInitialized());
8709 void InitOp::GetResponse(RequestResponse
& aResponse
) {
8710 AssertIsOnOwningThread();
8712 aResponse
= InitResponse();
8715 InitTemporaryStorageOp::InitTemporaryStorageOp()
8716 : QuotaRequestBase(/* aExclusive */ false) {
8717 AssertIsOnOwningThread();
8719 // Overwrite OriginOperationBase default values.
8720 mNeedsQuotaManagerInit
= true;
8721 mNeedsStorageInit
= false;
8724 void InitTemporaryStorageOp::Init(Quota
& aQuota
) { AssertIsOnOwningThread(); }
8726 nsresult
InitTemporaryStorageOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
8727 AssertIsOnIOThread();
8729 AUTO_PROFILER_LABEL("InitTemporaryStorageOp::DoDirectoryWork", OTHER
);
8731 QM_TRY(OkIf(aQuotaManager
.IsStorageInitialized()), NS_ERROR_FAILURE
;);
8733 QM_TRY(aQuotaManager
.EnsureTemporaryStorageIsInitialized());
8738 void InitTemporaryStorageOp::GetResponse(RequestResponse
& aResponse
) {
8739 AssertIsOnOwningThread();
8741 aResponse
= InitTemporaryStorageResponse();
8744 InitializeOriginRequestBase::InitializeOriginRequestBase(
8745 const PersistenceType aPersistenceType
, const PrincipalInfo
& aPrincipalInfo
)
8746 : QuotaRequestBase(/* aExclusive */ false), mCreated(false) {
8747 AssertIsOnOwningThread();
8749 auto principalMetadata
=
8750 QuotaManager::GetInfoFromValidatedPrincipalInfo(aPrincipalInfo
);
8752 // Overwrite OriginOperationBase default values.
8753 mNeedsQuotaManagerInit
= true;
8754 mNeedsStorageInit
= false;
8756 // Overwrite NormalOriginOperationBase default values.
8757 mPersistenceType
.SetValue(aPersistenceType
);
8758 mOriginScope
.SetFromOrigin(principalMetadata
.mOrigin
);
8760 // Overwrite InitializeOriginRequestBase default values.
8761 mSuffix
= std::move(principalMetadata
.mSuffix
);
8762 mGroup
= std::move(principalMetadata
.mGroup
);
8765 void InitializeOriginRequestBase::Init(Quota
& aQuota
) {
8766 AssertIsOnOwningThread();
8769 InitializePersistentOriginOp::InitializePersistentOriginOp(
8770 const RequestParams
& aParams
)
8771 : InitializeOriginRequestBase(
8772 PERSISTENCE_TYPE_PERSISTENT
,
8773 aParams
.get_InitializePersistentOriginParams().principalInfo()) {
8774 AssertIsOnOwningThread();
8775 MOZ_ASSERT(aParams
.type() ==
8776 RequestParams::TInitializePersistentOriginParams
);
8779 nsresult
InitializePersistentOriginOp::DoDirectoryWork(
8780 QuotaManager
& aQuotaManager
) {
8781 AssertIsOnIOThread();
8782 MOZ_ASSERT(!mPersistenceType
.IsNull());
8784 AUTO_PROFILER_LABEL("InitializePersistentOriginOp::DoDirectoryWork", OTHER
);
8786 QM_TRY(OkIf(aQuotaManager
.IsStorageInitialized()), NS_ERROR_FAILURE
);
8788 QM_TRY_UNWRAP(mCreated
,
8790 .EnsurePersistentOriginIsInitialized(OriginMetadata
{
8791 mSuffix
, mGroup
, nsCString
{mOriginScope
.GetOrigin()},
8792 PERSISTENCE_TYPE_PERSISTENT
})
8793 .map([](const auto& res
) { return res
.second
; })));
8798 void InitializePersistentOriginOp::GetResponse(RequestResponse
& aResponse
) {
8799 AssertIsOnOwningThread();
8801 aResponse
= InitializePersistentOriginResponse(mCreated
);
8804 InitializeTemporaryOriginOp::InitializeTemporaryOriginOp(
8805 const RequestParams
& aParams
)
8806 : InitializeOriginRequestBase(
8807 aParams
.get_InitializeTemporaryOriginParams().persistenceType(),
8808 aParams
.get_InitializeTemporaryOriginParams().principalInfo()) {
8809 AssertIsOnOwningThread();
8810 MOZ_ASSERT(aParams
.type() == RequestParams::TInitializeTemporaryOriginParams
);
8813 nsresult
InitializeTemporaryOriginOp::DoDirectoryWork(
8814 QuotaManager
& aQuotaManager
) {
8815 AssertIsOnIOThread();
8816 MOZ_ASSERT(!mPersistenceType
.IsNull());
8818 AUTO_PROFILER_LABEL("InitializeTemporaryOriginOp::DoDirectoryWork", OTHER
);
8820 QM_TRY(OkIf(aQuotaManager
.IsStorageInitialized()), NS_ERROR_FAILURE
);
8822 QM_TRY(OkIf(aQuotaManager
.IsTemporaryStorageInitialized()), NS_ERROR_FAILURE
);
8824 QM_TRY_UNWRAP(mCreated
,
8826 .EnsureTemporaryOriginIsInitialized(
8827 mPersistenceType
.Value(),
8828 OriginMetadata
{mSuffix
, mGroup
,
8829 nsCString
{mOriginScope
.GetOrigin()},
8830 mPersistenceType
.Value()})
8831 .map([](const auto& res
) { return res
.second
; })));
8836 void InitializeTemporaryOriginOp::GetResponse(RequestResponse
& aResponse
) {
8837 AssertIsOnOwningThread();
8839 aResponse
= InitializeTemporaryOriginResponse(mCreated
);
8842 ResetOrClearOp::ResetOrClearOp(bool aClear
)
8843 : QuotaRequestBase(/* aExclusive */ true), mClear(aClear
) {
8844 AssertIsOnOwningThread();
8846 // Overwrite OriginOperationBase default values.
8847 mNeedsQuotaManagerInit
= true;
8848 mNeedsStorageInit
= false;
8851 void ResetOrClearOp::Init(Quota
& aQuota
) { AssertIsOnOwningThread(); }
8853 void ResetOrClearOp::DeleteFiles(QuotaManager
& aQuotaManager
) {
8854 AssertIsOnIOThread();
8856 nsresult rv
= aQuotaManager
.AboutToClearOrigins(Nullable
<PersistenceType
>(),
8857 OriginScope::FromNull(),
8858 Nullable
<Client::Type
>());
8859 if (NS_WARN_IF(NS_FAILED(rv
))) {
8863 auto directoryOrErr
= QM_NewLocalFile(aQuotaManager
.GetStoragePath());
8864 if (NS_WARN_IF(directoryOrErr
.isErr())) {
8868 nsCOMPtr
<nsIFile
> directory
= directoryOrErr
.unwrap();
8870 rv
= directory
->Remove(true);
8871 if (rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
&&
8872 rv
!= NS_ERROR_FILE_NOT_FOUND
&& NS_FAILED(rv
)) {
8873 // This should never fail if we've closed all storage connections
8875 MOZ_ASSERT(false, "Failed to remove storage directory!");
8879 void ResetOrClearOp::DeleteStorageFile(QuotaManager
& aQuotaManager
) {
8880 AssertIsOnIOThread();
8882 QM_TRY_INSPECT(const auto& storageFile
,
8883 QM_NewLocalFile(aQuotaManager
.GetBasePath()), QM_VOID
);
8885 QM_TRY(storageFile
->Append(aQuotaManager
.GetStorageName() + kSQLiteSuffix
),
8888 const nsresult rv
= storageFile
->Remove(true);
8889 if (rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
&&
8890 rv
!= NS_ERROR_FILE_NOT_FOUND
&& NS_FAILED(rv
)) {
8891 // This should never fail if we've closed the storage connection
8893 MOZ_ASSERT(false, "Failed to remove storage file!");
8897 nsresult
ResetOrClearOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
8898 AssertIsOnIOThread();
8900 AUTO_PROFILER_LABEL("ResetOrClearOp::DoDirectoryWork", OTHER
);
8903 DeleteFiles(aQuotaManager
);
8905 aQuotaManager
.RemoveQuota();
8908 aQuotaManager
.ShutdownStorage();
8911 DeleteStorageFile(aQuotaManager
);
8917 void ResetOrClearOp::GetResponse(RequestResponse
& aResponse
) {
8918 AssertIsOnOwningThread();
8920 aResponse
= ClearAllResponse();
8922 aResponse
= ResetAllResponse();
8926 void ClearRequestBase::DeleteFiles(QuotaManager
& aQuotaManager
,
8927 PersistenceType aPersistenceType
) {
8928 AssertIsOnIOThread();
8930 QM_TRY(aQuotaManager
.AboutToClearOrigins(
8931 Nullable
<PersistenceType
>(aPersistenceType
), mOriginScope
,
8936 const auto& directory
,
8937 QM_NewLocalFile(aQuotaManager
.GetStoragePath(aPersistenceType
)), QM_VOID
);
8939 nsTArray
<nsCOMPtr
<nsIFile
>> directoriesForRemovalRetry
;
8941 aQuotaManager
.MaybeRecordQuotaManagerShutdownStep(
8942 "ClearRequestBase: Starting deleting files"_ns
);
8944 QM_TRY(CollectEachFile(
8948 OriginScope originScope
= mOriginScope
.Clone();
8949 if (originScope
.IsOrigin()) {
8950 originScope
.SetOrigin(
8951 MakeSanitizedOriginCString(originScope
.GetOrigin()));
8952 } else if (originScope
.IsPrefix()) {
8953 originScope
.SetOriginNoSuffix(MakeSanitizedOriginCString(
8954 originScope
.GetOriginNoSuffix()));
8958 aPersistenceType
, &aQuotaManager
, &directoriesForRemovalRetry
,
8959 this](nsCOMPtr
<nsIFile
>&& file
) -> mozilla::Result
<Ok
, nsresult
> {
8961 const auto& leafName
,
8962 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, file
, GetLeafName
));
8964 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*file
));
8966 switch (dirEntryKind
) {
8967 case nsIFileKind::ExistsAsDirectory
: {
8968 // Skip the origin directory if it doesn't match the pattern.
8969 if (!originScope
.Matches(OriginScope::FromOrigin(
8970 NS_ConvertUTF16toUTF8(leafName
)))) {
8975 const auto& metadata
,
8976 aQuotaManager
.LoadFullOriginMetadataWithRestore(file
));
8978 MOZ_ASSERT(metadata
.mPersistenceType
== aPersistenceType
);
8980 if (!mClientType
.IsNull()) {
8981 nsAutoString clientDirectoryName
;
8982 QM_TRY(OkIf(Client::TypeToText(mClientType
.Value(),
8983 clientDirectoryName
,
8985 Err(NS_ERROR_FAILURE
));
8987 QM_TRY(file
->Append(clientDirectoryName
));
8989 QM_TRY_INSPECT(const bool& exists
,
8990 MOZ_TO_RESULT_INVOKE(file
, Exists
));
8997 // We can't guarantee that this will always succeed on
8999 QM_WARNONLY_TRY(file
->Remove(true), [&](const auto&) {
9000 directoriesForRemovalRetry
.AppendElement(std::move(file
));
9003 const bool initialized
=
9004 aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
9005 ? aQuotaManager
.IsOriginInitialized(metadata
.mOrigin
)
9006 : aQuotaManager
.IsTemporaryStorageInitialized();
9008 // If it hasn't been initialized, we don't need to update the
9009 // quota and notify the removing client.
9014 if (aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
) {
9015 if (mClientType
.IsNull()) {
9016 aQuotaManager
.RemoveQuotaForOrigin(aPersistenceType
,
9019 aQuotaManager
.ResetUsageForClient(
9020 ClientMetadata
{metadata
, mClientType
.Value()});
9024 aQuotaManager
.OriginClearCompleted(
9025 aPersistenceType
, metadata
.mOrigin
, mClientType
);
9030 case nsIFileKind::ExistsAsFile
:
9031 // Unknown files during clearing are allowed. Just warn if we
9033 if (!IsOSMetadata(leafName
)) {
9034 UNKNOWN_FILE_WARNING(leafName
);
9039 case nsIFileKind::DoesNotExist
:
9040 // Ignore files that got removed externally while iterating.
9048 // Retry removing any directories that failed to be removed earlier now.
9050 // XXX This will still block this operation. We might instead dispatch a
9051 // runnable to our own thread for each retry round with a timer. We must
9052 // ensure that the directory lock is upheld until we complete or give up
9054 for (uint32_t index
= 0; index
< 10; index
++) {
9055 aQuotaManager
.MaybeRecordQuotaManagerShutdownStep(
9056 "ClearRequestBase: Retrying directory removal"_ns
);
9058 for (auto&& file
: std::exchange(directoriesForRemovalRetry
,
9059 nsTArray
<nsCOMPtr
<nsIFile
>>{})) {
9060 if (NS_FAILED((file
->Remove(true)))) {
9061 directoriesForRemovalRetry
.AppendElement(std::move(file
));
9065 if (directoriesForRemovalRetry
.IsEmpty()) {
9069 PR_Sleep(PR_MillisecondsToInterval(200));
9072 if (!directoriesForRemovalRetry
.IsEmpty()) {
9073 NS_WARNING("Failed to remove one or more directories, giving up!");
9076 aQuotaManager
.MaybeRecordQuotaManagerShutdownStep(
9077 "ClearRequestBase: Completed deleting files"_ns
);
9080 nsresult
ClearRequestBase::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
9081 AssertIsOnIOThread();
9082 aQuotaManager
.AssertStorageIsInitialized();
9084 AUTO_PROFILER_LABEL("ClearRequestBase::DoDirectoryWork", OTHER
);
9086 if (mPersistenceType
.IsNull()) {
9087 for (const PersistenceType type
: kAllPersistenceTypes
) {
9088 DeleteFiles(aQuotaManager
, type
);
9091 DeleteFiles(aQuotaManager
, mPersistenceType
.Value());
9097 ClearOriginOp::ClearOriginOp(const RequestParams
& aParams
)
9098 : ClearRequestBase(/* aExclusive */ true),
9099 mParams(aParams
.get_ClearOriginParams().commonParams()),
9100 mMatchAll(aParams
.get_ClearOriginParams().matchAll()) {
9101 MOZ_ASSERT(aParams
.type() == RequestParams::TClearOriginParams
);
9104 void ClearOriginOp::Init(Quota
& aQuota
) {
9105 AssertIsOnOwningThread();
9107 QuotaRequestBase::Init(aQuota
);
9109 if (mParams
.persistenceTypeIsExplicit()) {
9110 mPersistenceType
.SetValue(mParams
.persistenceType());
9113 // Figure out which origin we're dealing with.
9114 const auto origin
= QuotaManager::GetOriginFromValidatedPrincipalInfo(
9115 mParams
.principalInfo());
9118 mOriginScope
.SetFromPrefix(origin
);
9120 mOriginScope
.SetFromOrigin(origin
);
9123 if (mParams
.clientTypeIsExplicit()) {
9124 mClientType
.SetValue(mParams
.clientType());
9128 void ClearOriginOp::GetResponse(RequestResponse
& aResponse
) {
9129 AssertIsOnOwningThread();
9131 aResponse
= ClearOriginResponse();
9134 ClearDataOp::ClearDataOp(const RequestParams
& aParams
)
9135 : ClearRequestBase(/* aExclusive */ true), mParams(aParams
) {
9136 MOZ_ASSERT(aParams
.type() == RequestParams::TClearDataParams
);
9139 void ClearDataOp::Init(Quota
& aQuota
) {
9140 AssertIsOnOwningThread();
9142 QuotaRequestBase::Init(aQuota
);
9144 mOriginScope
.SetFromPattern(mParams
.pattern());
9147 void ClearDataOp::GetResponse(RequestResponse
& aResponse
) {
9148 AssertIsOnOwningThread();
9150 aResponse
= ClearDataResponse();
9153 ResetOriginOp::ResetOriginOp(const RequestParams
& aParams
)
9154 : QuotaRequestBase(/* aExclusive */ true) {
9155 AssertIsOnOwningThread();
9156 MOZ_ASSERT(aParams
.type() == RequestParams::TResetOriginParams
);
9158 const ClearResetOriginParams
& params
=
9159 aParams
.get_ResetOriginParams().commonParams();
9162 QuotaManager::GetOriginFromValidatedPrincipalInfo(params
.principalInfo());
9164 // Overwrite OriginOperationBase default values.
9165 mNeedsQuotaManagerInit
= true;
9166 mNeedsStorageInit
= false;
9168 // Overwrite NormalOriginOperationBase default values.
9169 if (params
.persistenceTypeIsExplicit()) {
9170 mPersistenceType
.SetValue(params
.persistenceType());
9173 mOriginScope
.SetFromOrigin(origin
);
9175 if (params
.clientTypeIsExplicit()) {
9176 mClientType
.SetValue(params
.clientType());
9180 void ResetOriginOp::Init(Quota
& aQuota
) { AssertIsOnOwningThread(); }
9182 nsresult
ResetOriginOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
9183 AssertIsOnIOThread();
9185 AUTO_PROFILER_LABEL("ResetOriginOp::DoDirectoryWork", OTHER
);
9187 // All the work is handled by NormalOriginOperationBase parent class. In this
9188 // particular case, we just needed to acquire an exclusive directory lock and
9194 void ResetOriginOp::GetResponse(RequestResponse
& aResponse
) {
9195 AssertIsOnOwningThread();
9197 aResponse
= ResetOriginResponse();
9200 PersistRequestBase::PersistRequestBase(const PrincipalInfo
& aPrincipalInfo
)
9201 : QuotaRequestBase(/* aExclusive */ false), mPrincipalInfo(aPrincipalInfo
) {
9202 AssertIsOnOwningThread();
9205 void PersistRequestBase::Init(Quota
& aQuota
) {
9206 AssertIsOnOwningThread();
9208 QuotaRequestBase::Init(aQuota
);
9210 mPersistenceType
.SetValue(PERSISTENCE_TYPE_DEFAULT
);
9212 // Figure out which origin we're dealing with.
9213 PrincipalMetadata principalMetadata
=
9214 QuotaManager::GetInfoFromValidatedPrincipalInfo(mPrincipalInfo
);
9216 mSuffix
= std::move(principalMetadata
.mSuffix
);
9217 mGroup
= std::move(principalMetadata
.mGroup
);
9218 mOriginScope
.SetFromOrigin(principalMetadata
.mOrigin
);
9221 PersistedOp::PersistedOp(const RequestParams
& aParams
)
9222 : PersistRequestBase(aParams
.get_PersistedParams().principalInfo()),
9224 MOZ_ASSERT(aParams
.type() == RequestParams::TPersistedParams
);
9227 nsresult
PersistedOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
9228 AssertIsOnIOThread();
9229 aQuotaManager
.AssertStorageIsInitialized();
9230 MOZ_ASSERT(!mPersistenceType
.IsNull());
9231 MOZ_ASSERT(mPersistenceType
.Value() == PERSISTENCE_TYPE_DEFAULT
);
9232 MOZ_ASSERT(mOriginScope
.IsOrigin());
9234 AUTO_PROFILER_LABEL("PersistedOp::DoDirectoryWork", OTHER
);
9236 Nullable
<bool> persisted
= aQuotaManager
.OriginPersisted(
9237 OriginMetadata
{mSuffix
, mGroup
, nsCString
{mOriginScope
.GetOrigin()},
9238 mPersistenceType
.Value()});
9240 if (!persisted
.IsNull()) {
9241 mPersisted
= persisted
.Value();
9245 // If we get here, it means the origin hasn't been initialized yet.
9246 // Try to get the persisted flag from directory metadata on disk.
9248 QM_TRY_INSPECT(const auto& directory
,
9249 aQuotaManager
.GetDirectoryForOrigin(mPersistenceType
.Value(),
9250 mOriginScope
.GetOrigin()));
9252 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(directory
, Exists
));
9255 // Get the metadata. We only use the persisted flag.
9256 QM_TRY_INSPECT(const auto& metadata
,
9257 aQuotaManager
.LoadFullOriginMetadataWithRestore(directory
));
9259 mPersisted
= metadata
.mPersisted
;
9261 // The directory has not been created yet.
9268 void PersistedOp::GetResponse(RequestResponse
& aResponse
) {
9269 AssertIsOnOwningThread();
9271 PersistedResponse persistedResponse
;
9272 persistedResponse
.persisted() = mPersisted
;
9274 aResponse
= persistedResponse
;
9277 PersistOp::PersistOp(const RequestParams
& aParams
)
9278 : PersistRequestBase(aParams
.get_PersistParams().principalInfo()) {
9279 MOZ_ASSERT(aParams
.type() == RequestParams::TPersistParams
);
9282 nsresult
PersistOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
9283 AssertIsOnIOThread();
9284 aQuotaManager
.AssertStorageIsInitialized();
9285 MOZ_ASSERT(!mPersistenceType
.IsNull());
9286 MOZ_ASSERT(mPersistenceType
.Value() == PERSISTENCE_TYPE_DEFAULT
);
9287 MOZ_ASSERT(mOriginScope
.IsOrigin());
9289 const OriginMetadata originMetadata
= {mSuffix
, mGroup
,
9290 nsCString
{mOriginScope
.GetOrigin()},
9291 mPersistenceType
.Value()};
9293 AUTO_PROFILER_LABEL("PersistOp::DoDirectoryWork", OTHER
);
9295 // Update directory metadata on disk first. Then, create/update the originInfo
9297 QM_TRY_INSPECT(const auto& directory
,
9298 aQuotaManager
.GetDirectoryForOrigin(mPersistenceType
.Value(),
9299 originMetadata
.mOrigin
));
9301 QM_TRY_INSPECT(const bool& created
,
9302 aQuotaManager
.EnsureOriginDirectory(*directory
));
9307 // Origin directory has been successfully created.
9308 // Create OriginInfo too if temporary storage was already initialized.
9309 if (aQuotaManager
.IsTemporaryStorageInitialized()) {
9310 timestamp
= aQuotaManager
.NoteOriginDirectoryCreated(
9311 originMetadata
, /* aPersisted */ true);
9313 timestamp
= PR_Now();
9316 QM_TRY(CreateDirectoryMetadata2(*directory
, timestamp
,
9317 /* aPersisted */ true, originMetadata
));
9319 // Get the metadata (restore the metadata file if necessary). We only use
9320 // the persisted flag.
9321 QM_TRY_INSPECT(const auto& metadata
,
9322 aQuotaManager
.LoadFullOriginMetadataWithRestore(directory
));
9324 if (!metadata
.mPersisted
) {
9325 QM_TRY_INSPECT(const auto& file
,
9327 *directory
, nsLiteralString(METADATA_V2_FILE_NAME
)));
9329 QM_TRY_INSPECT(const auto& stream
,
9330 GetBinaryOutputStream(*file
, FileFlag::Update
));
9334 // Update origin access time while we are here.
9335 QM_TRY(stream
->Write64(PR_Now()));
9337 // Set the persisted flag to true.
9338 QM_TRY(stream
->WriteBoolean(true));
9341 // Directory metadata has been successfully updated.
9342 // Update OriginInfo too if temporary storage was already initialized.
9343 if (aQuotaManager
.IsTemporaryStorageInitialized()) {
9344 aQuotaManager
.PersistOrigin(originMetadata
);
9351 void PersistOp::GetResponse(RequestResponse
& aResponse
) {
9352 AssertIsOnOwningThread();
9354 aResponse
= PersistResponse();
9357 EstimateOp::EstimateOp(const RequestParams
& aParams
)
9358 : QuotaRequestBase(/* aExclusive */ false), mUsage(0), mLimit(0) {
9359 AssertIsOnOwningThread();
9360 MOZ_ASSERT(aParams
.type() == RequestParams::TEstimateParams
);
9362 // XXX We don't use the quota info components other than the group here.
9363 mGroup
= std::move(QuotaManager::GetInfoFromValidatedPrincipalInfo(
9364 aParams
.get_EstimateParams().principalInfo())
9367 // Overwrite NormalOriginOperationBase default values.
9368 mNeedsDirectoryLocking
= false;
9370 // Overwrite OriginOperationBase default values.
9371 mNeedsQuotaManagerInit
= true;
9372 mNeedsStorageInit
= true;
9375 nsresult
EstimateOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
9376 AssertIsOnIOThread();
9377 aQuotaManager
.AssertStorageIsInitialized();
9379 AUTO_PROFILER_LABEL("EstimateOp::DoDirectoryWork", OTHER
);
9381 // Ensure temporary storage is initialized. If temporary storage hasn't been
9382 // initialized yet, the method will initialize it by traversing the
9383 // repositories for temporary and default storage (including origins belonging
9385 QM_TRY(aQuotaManager
.EnsureTemporaryStorageIsInitialized());
9387 // Get cached usage (the method doesn't have to stat any files).
9388 mUsage
= aQuotaManager
.GetGroupUsage(mGroup
);
9390 mLimit
= aQuotaManager
.GetGroupLimit();
9395 void EstimateOp::GetResponse(RequestResponse
& aResponse
) {
9396 AssertIsOnOwningThread();
9398 EstimateResponse estimateResponse
;
9400 estimateResponse
.usage() = mUsage
;
9401 estimateResponse
.limit() = mLimit
;
9403 aResponse
= estimateResponse
;
9406 ListOriginsOp::ListOriginsOp()
9407 : QuotaRequestBase(/* aExclusive */ false), TraverseRepositoryHelper() {
9408 AssertIsOnOwningThread();
9411 void ListOriginsOp::Init(Quota
& aQuota
) {
9412 AssertIsOnOwningThread();
9414 mNeedsQuotaManagerInit
= true;
9415 mNeedsStorageInit
= true;
9418 nsresult
ListOriginsOp::DoDirectoryWork(QuotaManager
& aQuotaManager
) {
9419 AssertIsOnIOThread();
9420 aQuotaManager
.AssertStorageIsInitialized();
9422 AUTO_PROFILER_LABEL("ListOriginsOp::DoDirectoryWork", OTHER
);
9424 for (const PersistenceType type
: kAllPersistenceTypes
) {
9425 QM_TRY(TraverseRepository(aQuotaManager
, type
));
9428 // TraverseRepository above only consulted the file-system to get a list of
9429 // known origins, but we also need to include origins that have pending quota
9432 aQuotaManager
.CollectPendingOriginsForListing([this](const auto& originInfo
) {
9433 mOrigins
.AppendElement(originInfo
->Origin());
9439 const Atomic
<bool>& ListOriginsOp::GetIsCanceledFlag() {
9440 AssertIsOnIOThread();
9445 nsresult
ListOriginsOp::ProcessOrigin(QuotaManager
& aQuotaManager
,
9446 nsIFile
& aOriginDir
,
9447 const bool aPersistent
,
9448 const PersistenceType aPersistenceType
) {
9449 AssertIsOnIOThread();
9451 // XXX We only use metadata.mOriginMetadata.mOrigin...
9452 QM_TRY_UNWRAP(auto metadata
,
9453 aQuotaManager
.LoadFullOriginMetadataWithRestore(&aOriginDir
));
9455 if (aQuotaManager
.IsOriginInternal(metadata
.mOrigin
)) {
9459 mOrigins
.AppendElement(std::move(metadata
.mOrigin
));
9464 void ListOriginsOp::GetResponse(RequestResponse
& aResponse
) {
9465 AssertIsOnOwningThread();
9467 aResponse
= ListOriginsResponse();
9468 if (mOrigins
.IsEmpty()) {
9472 nsTArray
<nsCString
>& origins
= aResponse
.get_ListOriginsResponse().origins();
9473 mOrigins
.SwapElements(origins
);
9476 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9479 already_AddRefed
<PrincipalVerifier
> PrincipalVerifier::CreateAndDispatch(
9480 nsTArray
<PrincipalInfo
>&& aPrincipalInfos
) {
9481 AssertIsOnIOThread();
9483 RefPtr
<PrincipalVerifier
> verifier
=
9484 new PrincipalVerifier(std::move(aPrincipalInfos
));
9486 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(verifier
));
9488 return verifier
.forget();
9491 Result
<Ok
, nsCString
> PrincipalVerifier::CheckPrincipalInfoValidity(
9492 const PrincipalInfo
& aPrincipalInfo
) {
9493 MOZ_ASSERT(NS_IsMainThread());
9495 switch (aPrincipalInfo
.type()) {
9496 // A system principal is acceptable.
9497 case PrincipalInfo::TSystemPrincipalInfo
: {
9501 case PrincipalInfo::TContentPrincipalInfo
: {
9502 const ContentPrincipalInfo
& info
=
9503 aPrincipalInfo
.get_ContentPrincipalInfo();
9505 nsCOMPtr
<nsIURI
> uri
;
9506 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), info
.spec());
9507 if (NS_WARN_IF(NS_FAILED(rv
))) {
9508 return Err("NS_NewURI failed"_ns
);
9511 nsCOMPtr
<nsIPrincipal
> principal
=
9512 BasePrincipal::CreateContentPrincipal(uri
, info
.attrs());
9513 if (NS_WARN_IF(!principal
)) {
9514 return Err("CreateContentPrincipal failed"_ns
);
9517 nsCString originNoSuffix
;
9518 rv
= principal
->GetOriginNoSuffix(originNoSuffix
);
9519 if (NS_WARN_IF(NS_FAILED(rv
))) {
9520 return Err("GetOriginNoSuffix failed"_ns
);
9523 if (NS_WARN_IF(originNoSuffix
!= info
.originNoSuffix())) {
9524 static const char messageTemplate
[] =
9525 "originNoSuffix (%s) doesn't match passed one (%s)!";
9527 QM_WARNING(messageTemplate
, originNoSuffix
.get(),
9528 info
.originNoSuffix().get());
9530 return Err(nsPrintfCString(
9531 messageTemplate
, AnonymizedOriginString(originNoSuffix
).get(),
9532 AnonymizedOriginString(info
.originNoSuffix()).get()));
9535 nsCString baseDomain
;
9536 rv
= principal
->GetBaseDomain(baseDomain
);
9537 if (NS_WARN_IF(NS_FAILED(rv
))) {
9538 return Err("GetBaseDomain failed"_ns
);
9541 if (NS_WARN_IF(baseDomain
!= info
.baseDomain())) {
9542 static const char messageTemplate
[] =
9543 "baseDomain (%s) doesn't match passed one (%s)!";
9545 QM_WARNING(messageTemplate
, baseDomain
.get(), info
.baseDomain().get());
9547 return Err(nsPrintfCString(messageTemplate
,
9548 AnonymizedCString(baseDomain
).get(),
9549 AnonymizedCString(info
.baseDomain()).get()));
9560 return Err("Null and expanded principals are not acceptable"_ns
);
9564 PrincipalVerifier::Run() {
9565 MOZ_ASSERT(NS_IsMainThread());
9567 nsAutoCString allDetails
;
9568 for (auto& principalInfo
: mPrincipalInfos
) {
9569 const auto res
= CheckPrincipalInfoValidity(principalInfo
);
9571 if (!allDetails
.IsEmpty()) {
9572 allDetails
.AppendLiteral(", ");
9575 allDetails
.Append(res
.inspectErr());
9579 if (!allDetails
.IsEmpty()) {
9580 allDetails
.Insert("Invalid principal infos found: ", 0);
9582 // In case of invalid principal infos, this will produce a crash reason such
9584 // Invalid principal infos found: originNoSuffix (https://aaa.aaaaaaa.aaa)
9585 // doesn't match passed one (about:aaaa)!
9587 // In case of errors while validating a principal, it will contain a
9588 // different message describing that error, which does not contain any
9589 // details of the actual principal info at the moment.
9591 // This string will be leaked.
9592 MOZ_CRASH_UNSAFE(strdup(allDetails
.BeginReading()));
9600 nsresult
StorageOperationBase::GetDirectoryMetadata(nsIFile
* aDirectory
,
9601 int64_t& aTimestamp
,
9603 nsACString
& aOrigin
,
9604 Nullable
<bool>& aIsApp
) {
9605 AssertIsOnIOThread();
9606 MOZ_ASSERT(aDirectory
);
9609 const auto& binaryStream
,
9610 GetBinaryInputStream(*aDirectory
, nsLiteralString(METADATA_FILE_NAME
)));
9612 QM_TRY_INSPECT(const uint64_t& timestamp
,
9613 MOZ_TO_RESULT_INVOKE(binaryStream
, Read64
));
9615 QM_TRY_INSPECT(const auto& group
, MOZ_TO_RESULT_INVOKE_TYPED(
9616 nsCString
, binaryStream
, ReadCString
));
9618 QM_TRY_INSPECT(const auto& origin
, MOZ_TO_RESULT_INVOKE_TYPED(
9619 nsCString
, binaryStream
, ReadCString
));
9621 Nullable
<bool> isApp
;
9623 if (NS_SUCCEEDED(binaryStream
->ReadBoolean(&value
))) {
9624 isApp
.SetValue(value
);
9627 aTimestamp
= timestamp
;
9630 aIsApp
= std::move(isApp
);
9634 nsresult
StorageOperationBase::GetDirectoryMetadata2(
9635 nsIFile
* aDirectory
, int64_t& aTimestamp
, nsACString
& aSuffix
,
9636 nsACString
& aGroup
, nsACString
& aOrigin
, bool& aIsApp
) {
9637 AssertIsOnIOThread();
9638 MOZ_ASSERT(aDirectory
);
9640 QM_TRY_INSPECT(const auto& binaryStream
,
9641 GetBinaryInputStream(*aDirectory
,
9642 nsLiteralString(METADATA_V2_FILE_NAME
)));
9644 QM_TRY_INSPECT(const uint64_t& timestamp
,
9645 MOZ_TO_RESULT_INVOKE(binaryStream
, Read64
));
9647 QM_TRY_INSPECT(const bool& persisted
,
9648 MOZ_TO_RESULT_INVOKE(binaryStream
, ReadBoolean
));
9649 Unused
<< persisted
;
9651 QM_TRY_INSPECT(const bool& reservedData1
,
9652 MOZ_TO_RESULT_INVOKE(binaryStream
, Read32
));
9653 Unused
<< reservedData1
;
9655 QM_TRY_INSPECT(const bool& reservedData2
,
9656 MOZ_TO_RESULT_INVOKE(binaryStream
, Read32
));
9657 Unused
<< reservedData2
;
9659 QM_TRY_INSPECT(const auto& suffix
, MOZ_TO_RESULT_INVOKE_TYPED(
9660 nsCString
, binaryStream
, ReadCString
));
9662 QM_TRY_INSPECT(const auto& group
, MOZ_TO_RESULT_INVOKE_TYPED(
9663 nsCString
, binaryStream
, ReadCString
));
9665 QM_TRY_INSPECT(const auto& origin
, MOZ_TO_RESULT_INVOKE_TYPED(
9666 nsCString
, binaryStream
, ReadCString
));
9668 QM_TRY_INSPECT(const bool& isApp
,
9669 MOZ_TO_RESULT_INVOKE(binaryStream
, ReadBoolean
));
9671 aTimestamp
= timestamp
;
9679 int64_t StorageOperationBase::GetOriginLastModifiedTime(
9680 const OriginProps
& aOriginProps
) {
9681 return GetLastModifiedTime(*aOriginProps
.mPersistenceType
,
9682 *aOriginProps
.mDirectory
);
9685 nsresult
StorageOperationBase::RemoveObsoleteOrigin(
9686 const OriginProps
& aOriginProps
) {
9687 AssertIsOnIOThread();
9690 "Deleting obsolete %s directory that is no longer a legal "
9692 NS_ConvertUTF16toUTF8(aOriginProps
.mLeafName
).get());
9694 QM_TRY(aOriginProps
.mDirectory
->Remove(/* recursive */ true));
9699 Result
<bool, nsresult
> StorageOperationBase::MaybeRenameOrigin(
9700 const OriginProps
& aOriginProps
) {
9701 AssertIsOnIOThread();
9703 const nsAString
& oldLeafName
= aOriginProps
.mLeafName
;
9705 const auto newLeafName
=
9706 MakeSanitizedOriginString(aOriginProps
.mOriginMetadata
.mOrigin
);
9708 if (oldLeafName
== newLeafName
) {
9712 QM_TRY(CreateDirectoryMetadata(*aOriginProps
.mDirectory
,
9713 aOriginProps
.mTimestamp
,
9714 aOriginProps
.mOriginMetadata
));
9716 QM_TRY(CreateDirectoryMetadata2(
9717 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
9718 /* aPersisted */ false, aOriginProps
.mOriginMetadata
));
9720 QM_TRY_INSPECT(const auto& newFile
,
9721 MOZ_TO_RESULT_INVOKE_TYPED(
9722 nsCOMPtr
<nsIFile
>, *aOriginProps
.mDirectory
, GetParent
));
9724 QM_TRY(newFile
->Append(newLeafName
));
9726 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(newFile
, Exists
));
9730 "Can't rename %s directory to %s, the target already exists, removing "
9731 "instead of renaming!",
9732 NS_ConvertUTF16toUTF8(oldLeafName
).get(),
9733 NS_ConvertUTF16toUTF8(newLeafName
).get());
9736 QM_TRY(CallWithDelayedRetriesIfAccessDenied(
9737 [&exists
, &aOriginProps
, &newLeafName
] {
9739 QM_TRY_RETURN(aOriginProps
.mDirectory
->Remove(/* recursive */ true));
9741 QM_TRY_RETURN(aOriginProps
.mDirectory
->RenameTo(nullptr, newLeafName
));
9743 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
9744 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
9749 nsresult
StorageOperationBase::ProcessOriginDirectories() {
9750 AssertIsOnIOThread();
9751 MOZ_ASSERT(!mOriginProps
.IsEmpty());
9753 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9754 nsTArray
<PrincipalInfo
> principalInfos
;
9757 for (auto& originProps
: mOriginProps
) {
9758 switch (originProps
.mType
) {
9759 case OriginProps::eChrome
: {
9760 originProps
.mOriginMetadata
= {QuotaManager::GetInfoForChrome(),
9761 *originProps
.mPersistenceType
};
9765 case OriginProps::eContent
: {
9766 RefPtr
<MozURL
> specURL
;
9767 nsresult rv
= MozURL::Init(getter_AddRefs(specURL
), originProps
.mSpec
);
9768 if (NS_WARN_IF(NS_FAILED(rv
))) {
9769 // If a URL cannot be understood by MozURL during restoring or
9770 // upgrading, either marking the directory as broken or removing that
9771 // corresponding directory should be considered. While the cost of
9772 // marking the directory as broken during a upgrade is too high,
9773 // removing the directory is a better choice rather than blocking the
9774 // initialization or the upgrade.
9776 "A URL (%s) for the origin directory is not recognized by "
9777 "MozURL. The directory will be deleted for now to pass the "
9778 "initialization or the upgrade.",
9779 originProps
.mSpec
.get());
9781 originProps
.mType
= OriginProps::eObsolete
;
9785 nsCString originNoSuffix
;
9786 specURL
->Origin(originNoSuffix
);
9789 const auto& baseDomain
,
9790 MOZ_TO_RESULT_INVOKE_TYPED(nsCString
, specURL
, BaseDomain
));
9792 ContentPrincipalInfo contentPrincipalInfo
;
9793 contentPrincipalInfo
.attrs() = originProps
.mAttrs
;
9794 contentPrincipalInfo
.originNoSuffix() = originNoSuffix
;
9795 contentPrincipalInfo
.spec() = originProps
.mSpec
;
9796 contentPrincipalInfo
.baseDomain() = baseDomain
;
9798 PrincipalInfo
principalInfo(contentPrincipalInfo
);
9800 originProps
.mOriginMetadata
= {
9801 QuotaManager::GetInfoFromValidatedPrincipalInfo(principalInfo
),
9802 *originProps
.mPersistenceType
};
9804 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9805 principalInfos
.AppendElement(principalInfo
);
9811 case OriginProps::eObsolete
: {
9812 // There's no way to get info for obsolete origins.
9817 MOZ_CRASH("Bad type!");
9821 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9822 if (!principalInfos
.IsEmpty()) {
9823 RefPtr
<PrincipalVerifier
> principalVerifier
=
9824 PrincipalVerifier::CreateAndDispatch(std::move(principalInfos
));
9828 // Don't try to upgrade obsolete origins, remove them right after we detect
9830 for (const auto& originProps
: mOriginProps
) {
9831 if (originProps
.mType
== OriginProps::eObsolete
) {
9832 MOZ_ASSERT(originProps
.mOriginMetadata
.mSuffix
.IsEmpty());
9833 MOZ_ASSERT(originProps
.mOriginMetadata
.mGroup
.IsEmpty());
9834 MOZ_ASSERT(originProps
.mOriginMetadata
.mOrigin
.IsEmpty());
9836 QM_TRY(RemoveObsoleteOrigin(originProps
));
9838 MOZ_ASSERT(!originProps
.mOriginMetadata
.mGroup
.IsEmpty());
9839 MOZ_ASSERT(!originProps
.mOriginMetadata
.mOrigin
.IsEmpty());
9841 QM_TRY(ProcessOriginDirectory(originProps
));
9848 // XXX Do the fallible initialization in a separate non-static member function
9849 // of StorageOperationBase and eventually get rid of this method and use a
9850 // normal constructor instead.
9851 template <typename PersistenceTypeFunc
>
9852 nsresult
StorageOperationBase::OriginProps::Init(
9853 PersistenceTypeFunc
&& aPersistenceTypeFunc
) {
9854 AssertIsOnIOThread();
9857 const auto& leafName
,
9858 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, *mDirectory
, GetLeafName
));
9861 OriginAttributes attrs
;
9862 nsCString originalSuffix
;
9863 OriginParser::ResultType result
= OriginParser::ParseOrigin(
9864 NS_ConvertUTF16toUTF8(leafName
), spec
, &attrs
, originalSuffix
);
9865 if (NS_WARN_IF(result
== OriginParser::InvalidOrigin
)) {
9866 mType
= OriginProps::eInvalid
;
9870 const auto persistenceType
= [&]() -> PersistenceType
{
9871 // XXX We shouldn't continue with initialization if OriginParser returned
9872 // anything else but ValidOrigin. Otherwise, we have to deal with empty
9873 // spec when the origin is obsolete, like here. The caller should handle
9874 // the errors. Until it's fixed, we have to treat obsolete origins as
9875 // origins with unknown/invalid persistence type.
9876 if (result
!= OriginParser::ValidOrigin
) {
9877 return PERSISTENCE_TYPE_INVALID
;
9879 return std::forward
<PersistenceTypeFunc
>(aPersistenceTypeFunc
)(spec
);
9882 mLeafName
= leafName
;
9885 mOriginalSuffix
= originalSuffix
;
9886 mPersistenceType
.init(persistenceType
);
9887 if (result
== OriginParser::ObsoleteOrigin
) {
9889 } else if (mSpec
.EqualsLiteral(kChromeOrigin
)) {
9899 auto OriginParser::ParseOrigin(const nsACString
& aOrigin
, nsCString
& aSpec
,
9900 OriginAttributes
* aAttrs
,
9901 nsCString
& aOriginalSuffix
) -> ResultType
{
9902 MOZ_ASSERT(!aOrigin
.IsEmpty());
9905 nsCString
origin(aOrigin
);
9906 int32_t pos
= origin
.RFindChar('^');
9908 if (pos
== kNotFound
) {
9909 aOriginalSuffix
.Truncate();
9911 aOriginalSuffix
= Substring(origin
, pos
);
9914 OriginAttributes originAttributes
;
9916 nsCString originNoSuffix
;
9917 bool ok
= originAttributes
.PopulateFromOrigin(aOrigin
, originNoSuffix
);
9919 return InvalidOrigin
;
9922 OriginParser
parser(originNoSuffix
);
9924 *aAttrs
= originAttributes
;
9925 return parser
.Parse(aSpec
);
9928 auto OriginParser::Parse(nsACString
& aSpec
) -> ResultType
{
9929 while (mTokenizer
.hasMoreTokens()) {
9930 const nsDependentCSubstring
& token
= mTokenizer
.nextToken();
9938 if (!mHandledTokens
.IsEmpty()) {
9939 mHandledTokens
.AppendLiteral(", ");
9941 mHandledTokens
.Append('\'');
9942 mHandledTokens
.Append(token
);
9943 mHandledTokens
.Append('\'');
9946 if (!mError
&& mTokenizer
.separatorAfterCurrentToken()) {
9947 HandleTrailingSeparator();
9951 QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin
.get(),
9952 mHandledTokens
.get());
9954 return (mSchemeType
== eChrome
|| mSchemeType
== eAbout
) ? ObsoleteOrigin
9958 MOZ_ASSERT(mState
== eComplete
|| mState
== eHandledTrailingSeparator
);
9960 // For IPv6 URL, it should at least have three groups.
9961 MOZ_ASSERT_IF(mIPGroup
> 0, mIPGroup
>= 3);
9963 nsAutoCString
spec(mScheme
);
9965 if (mSchemeType
== eFile
) {
9966 spec
.AppendLiteral("://");
9968 if (mUniversalFileOrigin
) {
9969 MOZ_ASSERT(mPathnameComponents
.Length() == 1);
9971 spec
.Append(mPathnameComponents
[0]);
9973 for (uint32_t count
= mPathnameComponents
.Length(), index
= 0;
9974 index
< count
; index
++) {
9976 spec
.Append(mPathnameComponents
[index
]);
9985 if (mSchemeType
== eAbout
) {
9986 if (mMaybeObsolete
) {
9987 // The "moz-safe-about+++home" was acciedntally created by a buggy nightly
9988 // and can be safely removed.
9989 return mHost
.EqualsLiteral("home") ? ObsoleteOrigin
: InvalidOrigin
;
9992 } else if (mSchemeType
!= eChrome
) {
9993 spec
.AppendLiteral("://");
9998 if (!mPort
.IsNull()) {
10000 spec
.AppendInt(mPort
.Value());
10005 return mScheme
.EqualsLiteral("app") ? ObsoleteOrigin
: ValidOrigin
;
10008 void OriginParser::HandleScheme(const nsDependentCSubstring
& aToken
) {
10009 MOZ_ASSERT(!aToken
.IsEmpty());
10010 MOZ_ASSERT(mState
== eExpectingAppIdOrScheme
|| mState
== eExpectingScheme
);
10012 bool isAbout
= false;
10013 bool isMozSafeAbout
= false;
10014 bool isFile
= false;
10015 bool isChrome
= false;
10016 if (aToken
.EqualsLiteral("http") || aToken
.EqualsLiteral("https") ||
10017 (isAbout
= aToken
.EqualsLiteral("about") ||
10018 (isMozSafeAbout
= aToken
.EqualsLiteral("moz-safe-about"))) ||
10019 aToken
.EqualsLiteral("indexeddb") ||
10020 (isFile
= aToken
.EqualsLiteral("file")) || aToken
.EqualsLiteral("app") ||
10021 aToken
.EqualsLiteral("resource") ||
10022 aToken
.EqualsLiteral("moz-extension") ||
10023 (isChrome
= aToken
.EqualsLiteral(kChromeOrigin
))) {
10027 mSchemeType
= eAbout
;
10028 mState
= isMozSafeAbout
? eExpectingEmptyToken1OrHost
: eExpectingHost
;
10029 } else if (isChrome
) {
10030 mSchemeType
= eChrome
;
10031 if (mTokenizer
.hasMoreTokens()) {
10034 mState
= eComplete
;
10037 mSchemeType
= eFile
;
10039 mState
= eExpectingEmptyToken1
;
10045 QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken
).get());
10050 void OriginParser::HandlePathnameComponent(
10051 const nsDependentCSubstring
& aToken
) {
10052 MOZ_ASSERT(!aToken
.IsEmpty());
10053 MOZ_ASSERT(mState
== eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
||
10054 mState
== eExpectingEmptyTokenOrPathnameComponent
);
10055 MOZ_ASSERT(mSchemeType
== eFile
);
10057 mPathnameComponents
.AppendElement(aToken
);
10059 mState
= mTokenizer
.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
10063 void OriginParser::HandleToken(const nsDependentCSubstring
& aToken
) {
10065 case eExpectingAppIdOrScheme
: {
10066 if (aToken
.IsEmpty()) {
10067 QM_WARNING("Expected an app id or scheme (not an empty string)!");
10073 if (IsAsciiDigit(aToken
.First())) {
10074 // nsDependentCSubstring doesn't provice ToInteger()
10075 nsCString
token(aToken
);
10078 Unused
<< token
.ToInteger(&rv
);
10079 if (NS_SUCCEEDED(rv
)) {
10080 mState
= eExpectingInMozBrowser
;
10085 HandleScheme(aToken
);
10090 case eExpectingInMozBrowser
: {
10091 if (aToken
.Length() != 1) {
10092 QM_WARNING("'%d' is not a valid length for the inMozBrowser flag!",
10099 if (aToken
.First() == 't') {
10100 mInIsolatedMozBrowser
= true;
10101 } else if (aToken
.First() == 'f') {
10102 mInIsolatedMozBrowser
= false;
10104 QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
10105 nsCString(aToken
).get());
10111 mState
= eExpectingScheme
;
10116 case eExpectingScheme
: {
10117 if (aToken
.IsEmpty()) {
10118 QM_WARNING("Expected a scheme (not an empty string)!");
10124 HandleScheme(aToken
);
10129 case eExpectingEmptyToken1
: {
10130 if (!aToken
.IsEmpty()) {
10131 QM_WARNING("Expected the first empty token!");
10137 mState
= eExpectingEmptyToken2
;
10142 case eExpectingEmptyToken2
: {
10143 if (!aToken
.IsEmpty()) {
10144 QM_WARNING("Expected the second empty token!");
10150 if (mSchemeType
== eFile
) {
10151 mState
= eExpectingEmptyTokenOrUniversalFileOrigin
;
10153 if (mSchemeType
== eAbout
) {
10154 mMaybeObsolete
= true;
10156 mState
= eExpectingHost
;
10162 case eExpectingEmptyTokenOrUniversalFileOrigin
: {
10163 MOZ_ASSERT(mSchemeType
== eFile
);
10165 if (aToken
.IsEmpty()) {
10166 mState
= mTokenizer
.hasMoreTokens()
10167 ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
10173 if (aToken
.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) {
10174 mUniversalFileOrigin
= true;
10176 mPathnameComponents
.AppendElement(aToken
);
10178 mState
= eComplete
;
10184 "Expected the third empty token or "
10185 "UNIVERSAL_FILE_URI_ORIGIN!");
10191 case eExpectingHost
: {
10192 if (aToken
.IsEmpty()) {
10193 QM_WARNING("Expected a host (not an empty string)!");
10201 if (aToken
.First() == '[') {
10202 MOZ_ASSERT(mIPGroup
== 0);
10205 mState
= eExpectingIPV6Token
;
10207 MOZ_ASSERT(mTokenizer
.hasMoreTokens());
10211 if (mTokenizer
.hasMoreTokens()) {
10212 if (mSchemeType
== eAbout
) {
10213 QM_WARNING("Expected an empty string after host!");
10219 mState
= eExpectingPort
;
10224 mState
= eComplete
;
10229 case eExpectingPort
: {
10230 MOZ_ASSERT(mSchemeType
== eNone
);
10232 if (aToken
.IsEmpty()) {
10233 QM_WARNING("Expected a port (not an empty string)!");
10239 // nsDependentCSubstring doesn't provice ToInteger()
10240 nsCString
token(aToken
);
10243 uint32_t port
= token
.ToInteger(&rv
);
10244 if (NS_SUCCEEDED(rv
)) {
10245 mPort
.SetValue() = port
;
10247 QM_WARNING("'%s' is not a valid port number!", token
.get());
10253 mState
= eComplete
;
10258 case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
: {
10259 MOZ_ASSERT(mSchemeType
== eFile
);
10261 if (aToken
.IsEmpty()) {
10262 mPathnameComponents
.AppendElement(""_ns
);
10264 mState
= mTokenizer
.hasMoreTokens()
10265 ? eExpectingEmptyTokenOrPathnameComponent
10271 if (aToken
.Length() == 1 && IsAsciiAlpha(aToken
.First())) {
10272 mMaybeDriveLetter
= true;
10274 mPathnameComponents
.AppendElement(aToken
);
10276 mState
= mTokenizer
.hasMoreTokens()
10277 ? eExpectingEmptyTokenOrPathnameComponent
10283 HandlePathnameComponent(aToken
);
10288 case eExpectingEmptyTokenOrPathnameComponent
: {
10289 MOZ_ASSERT(mSchemeType
== eFile
);
10291 if (aToken
.IsEmpty()) {
10292 if (mMaybeDriveLetter
) {
10293 MOZ_ASSERT(mPathnameComponents
.Length() == 1);
10295 nsCString
& pathnameComponent
= mPathnameComponents
[0];
10296 pathnameComponent
.Append(':');
10298 mMaybeDriveLetter
= false;
10300 mPathnameComponents
.AppendElement(""_ns
);
10303 mState
= mTokenizer
.hasMoreTokens()
10304 ? eExpectingEmptyTokenOrPathnameComponent
10310 HandlePathnameComponent(aToken
);
10315 case eExpectingEmptyToken1OrHost
: {
10316 MOZ_ASSERT(mSchemeType
== eAbout
&&
10317 mScheme
.EqualsLiteral("moz-safe-about"));
10319 if (aToken
.IsEmpty()) {
10320 mState
= eExpectingEmptyToken2
;
10323 mState
= mTokenizer
.hasMoreTokens() ? eExpectingPort
: eComplete
;
10329 case eExpectingIPV6Token
: {
10330 // A safe check for preventing infinity recursion.
10331 if (++mIPGroup
> 8) {
10336 mHost
.AppendLiteral(":");
10337 mHost
.Append(aToken
);
10338 if (!aToken
.IsEmpty() && aToken
.Last() == ']') {
10339 mState
= mTokenizer
.hasMoreTokens() ? eExpectingPort
: eComplete
;
10346 MOZ_CRASH("Should never get here!");
10350 void OriginParser::HandleTrailingSeparator() {
10351 MOZ_ASSERT(mState
== eComplete
);
10352 MOZ_ASSERT(mSchemeType
== eFile
);
10354 mPathnameComponents
.AppendElement(""_ns
);
10356 mState
= eHandledTrailingSeparator
;
10359 nsresult
RepositoryOperationBase::ProcessRepository() {
10360 AssertIsOnIOThread();
10364 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(mDirectory
, Exists
),
10365 QM_ASSERT_UNREACHABLE
);
10366 MOZ_ASSERT(exists
);
10370 QM_TRY(CollectEachFileEntry(
10372 [](const auto& originFile
) -> Result
<mozilla::Ok
, nsresult
> {
10374 const auto& leafName
,
10375 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, originFile
, GetLeafName
));
10377 // Unknown files during upgrade are allowed. Just warn if we find
10379 if (!IsOSMetadata(leafName
)) {
10380 UNKNOWN_FILE_WARNING(leafName
);
10383 return mozilla::Ok
{};
10385 [&self
= *this](const auto& originDir
) -> Result
<mozilla::Ok
, nsresult
> {
10386 OriginProps
originProps(WrapMovingNotNullUnchecked(originDir
));
10387 QM_TRY(originProps
.Init([&self
](const auto& aSpec
) {
10388 return self
.PersistenceTypeFromSpec(aSpec
);
10390 // Bypass invalid origins while upgrading
10391 QM_TRY(OkIf(originProps
.mType
!= OriginProps::eInvalid
), mozilla::Ok
{});
10393 if (originProps
.mType
!= OriginProps::eObsolete
) {
10395 const bool& removed
,
10396 MOZ_TO_RESULT_INVOKE(self
, PrepareOriginDirectory
, originProps
));
10398 return mozilla::Ok
{};
10402 self
.mOriginProps
.AppendElement(std::move(originProps
));
10404 return mozilla::Ok
{};
10407 if (mOriginProps
.IsEmpty()) {
10411 QM_TRY(ProcessOriginDirectories());
10416 template <typename UpgradeMethod
>
10417 nsresult
RepositoryOperationBase::MaybeUpgradeClients(
10418 const OriginProps
& aOriginProps
, UpgradeMethod aMethod
) {
10419 AssertIsOnIOThread();
10420 MOZ_ASSERT(aMethod
);
10422 QuotaManager
* quotaManager
= QuotaManager::Get();
10423 MOZ_ASSERT(quotaManager
);
10425 QM_TRY(CollectEachFileEntry(
10426 *aOriginProps
.mDirectory
,
10427 [](const auto& file
) -> Result
<mozilla::Ok
, nsresult
> {
10429 const auto& leafName
,
10430 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, file
, GetLeafName
));
10432 if (!IsOriginMetadata(leafName
) && !IsTempMetadata(leafName
)) {
10433 UNKNOWN_FILE_WARNING(leafName
);
10436 return mozilla::Ok
{};
10438 [quotaManager
, &aMethod
,
10439 &self
= *this](const auto& dir
) -> Result
<mozilla::Ok
, nsresult
> {
10441 const auto& leafName
,
10442 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, dir
, GetLeafName
));
10445 const bool& removed
,
10446 MOZ_TO_RESULT_INVOKE(self
, PrepareClientDirectory
, dir
, leafName
));
10448 return mozilla::Ok
{};
10451 Client::Type clientType
;
10452 bool ok
= Client::TypeFromText(leafName
, clientType
, fallible
);
10454 UNKNOWN_FILE_WARNING(leafName
);
10455 return mozilla::Ok
{};
10458 Client
* client
= quotaManager
->GetClient(clientType
);
10459 MOZ_ASSERT(client
);
10461 QM_TRY((client
->*aMethod
)(dir
));
10463 return mozilla::Ok
{};
10469 nsresult
RepositoryOperationBase::PrepareClientDirectory(
10470 nsIFile
* aFile
, const nsAString
& aLeafName
, bool& aRemoved
) {
10471 AssertIsOnIOThread();
10477 nsresult
CreateOrUpgradeDirectoryMetadataHelper::Init() {
10478 AssertIsOnIOThread();
10479 MOZ_ASSERT(mDirectory
);
10481 const auto maybeLegacyPersistenceType
=
10482 LegacyPersistenceTypeFromFile(*mDirectory
, fallible
);
10483 QM_TRY(OkIf(maybeLegacyPersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
10485 mLegacyPersistenceType
.init(maybeLegacyPersistenceType
.value());
10490 Maybe
<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType
>
10491 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
10492 nsIFile
& aFile
, const fallible_t
&) {
10493 nsAutoString leafName
;
10494 MOZ_ALWAYS_SUCCEEDS(aFile
.GetLeafName(leafName
));
10496 if (leafName
.Equals(u
"persistent"_ns
)) {
10497 return Some(LegacyPersistenceType::Persistent
);
10500 if (leafName
.Equals(u
"temporary"_ns
)) {
10501 return Some(LegacyPersistenceType::Temporary
);
10508 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
10509 const nsCString
& aSpec
) {
10510 if (QuotaManager::IsOriginInternal(aSpec
)) {
10511 return PERSISTENCE_TYPE_PERSISTENT
;
10514 return PERSISTENCE_TYPE_DEFAULT
;
10517 PersistenceType
CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
10518 const nsCString
& aSpec
) {
10519 switch (*mLegacyPersistenceType
) {
10520 case LegacyPersistenceType::Persistent
:
10521 return PersistenceTypeFromLegacyPersistentSpec(aSpec
);
10522 case LegacyPersistenceType::Temporary
:
10523 return PERSISTENCE_TYPE_TEMPORARY
;
10525 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
10528 nsresult
CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
10529 nsIFile
* aDirectory
) {
10530 AssertIsOnIOThread();
10531 MOZ_ASSERT(aDirectory
);
10534 const auto& metadataFile
,
10535 CloneFileAndAppend(*aDirectory
, nsLiteralString(METADATA_FILE_NAME
)));
10537 QM_TRY_INSPECT(const bool& exists
,
10538 MOZ_TO_RESULT_INVOKE(metadataFile
, Exists
));
10541 // Directory structure upgrade needed.
10542 // Move all files to IDB specific directory.
10544 nsString idbDirectoryName
;
10545 QM_TRY(OkIf(Client::TypeToText(Client::IDB
, idbDirectoryName
, fallible
)),
10548 QM_TRY_INSPECT(const auto& idbDirectory
,
10549 CloneFileAndAppend(*aDirectory
, idbDirectoryName
));
10551 QM_TRY(QM_OR_ELSE_WARN(
10552 ToResult(idbDirectory
->Create(nsIFile::DIRECTORY_TYPE
, 0755)),
10553 ([&idbDirectory
](const nsresult rv
) -> Result
<Ok
, nsresult
> {
10554 if (rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
10555 QM_TRY_INSPECT(const bool& isDirectory
,
10556 MOZ_TO_RESULT_INVOKE(idbDirectory
, IsDirectory
));
10558 QM_TRY(OkIf(isDirectory
), Err(NS_ERROR_UNEXPECTED
));
10566 QM_TRY(CollectEachFile(
10568 [&idbDirectory
, &idbDirectoryName
](
10569 const nsCOMPtr
<nsIFile
>& file
) -> Result
<Ok
, nsresult
> {
10571 const auto& leafName
,
10572 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString
, file
, GetLeafName
));
10574 if (!leafName
.Equals(idbDirectoryName
)) {
10575 QM_TRY(file
->MoveTo(idbDirectory
, u
""_ns
));
10581 QM_TRY(metadataFile
->Create(nsIFile::NORMAL_FILE_TYPE
, 0644));
10587 nsresult
CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
10588 OriginProps
& aOriginProps
, bool* aRemoved
) {
10589 AssertIsOnIOThread();
10590 MOZ_ASSERT(aRemoved
);
10592 if (*mLegacyPersistenceType
== LegacyPersistenceType::Persistent
) {
10593 QM_TRY(MaybeUpgradeOriginDirectory(aOriginProps
.mDirectory
.get()));
10595 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
10600 Nullable
<bool> isApp
;
10601 nsresult rv
= GetDirectoryMetadata(aOriginProps
.mDirectory
.get(), timestamp
,
10602 group
, origin
, isApp
);
10603 if (NS_FAILED(rv
)) {
10604 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
10605 aOriginProps
.mNeedsRestore
= true;
10606 } else if (!isApp
.IsNull()) {
10607 aOriginProps
.mIgnore
= true;
10615 nsresult
CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
10616 const OriginProps
& aOriginProps
) {
10617 AssertIsOnIOThread();
10619 if (*mLegacyPersistenceType
== LegacyPersistenceType::Persistent
) {
10620 QM_TRY(CreateDirectoryMetadata(*aOriginProps
.mDirectory
,
10621 aOriginProps
.mTimestamp
,
10622 aOriginProps
.mOriginMetadata
));
10624 // Move internal origins to new persistent storage.
10625 if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps
.mSpec
) ==
10626 PERSISTENCE_TYPE_PERSISTENT
) {
10627 if (!mPermanentStorageDir
) {
10628 QuotaManager
* quotaManager
= QuotaManager::Get();
10629 MOZ_ASSERT(quotaManager
);
10631 const nsString
& permanentStoragePath
=
10632 quotaManager
->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT
);
10634 QM_TRY_UNWRAP(mPermanentStorageDir
,
10635 QM_NewLocalFile(permanentStoragePath
));
10638 const nsAString
& leafName
= aOriginProps
.mLeafName
;
10640 QM_TRY_INSPECT(const auto& newDirectory
,
10641 CloneFileAndAppend(*mPermanentStorageDir
, leafName
));
10643 QM_TRY_INSPECT(const bool& exists
,
10644 MOZ_TO_RESULT_INVOKE(newDirectory
, Exists
));
10647 QM_WARNING("Found %s in storage/persistent and storage/permanent !",
10648 NS_ConvertUTF16toUTF8(leafName
).get());
10650 QM_TRY(aOriginProps
.mDirectory
->Remove(/* recursive */ true));
10652 QM_TRY(aOriginProps
.mDirectory
->MoveTo(mPermanentStorageDir
, u
""_ns
));
10655 } else if (aOriginProps
.mNeedsRestore
) {
10656 QM_TRY(CreateDirectoryMetadata(*aOriginProps
.mDirectory
,
10657 aOriginProps
.mTimestamp
,
10658 aOriginProps
.mOriginMetadata
));
10659 } else if (!aOriginProps
.mIgnore
) {
10660 QM_TRY_INSPECT(const auto& file
,
10661 CloneFileAndAppend(*aOriginProps
.mDirectory
,
10662 nsLiteralString(METADATA_FILE_NAME
)));
10664 QM_TRY_INSPECT(const auto& stream
,
10665 GetBinaryOutputStream(*file
, FileFlag::Append
));
10667 MOZ_ASSERT(stream
);
10669 // Currently unused (used to be isApp).
10670 QM_TRY(stream
->WriteBoolean(false));
10676 nsresult
UpgradeStorageHelperBase::Init() {
10677 AssertIsOnIOThread();
10678 MOZ_ASSERT(mDirectory
);
10680 const auto maybePersistenceType
=
10681 PersistenceTypeFromFile(*mDirectory
, fallible
);
10682 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
10684 mPersistenceType
.init(maybePersistenceType
.value());
10689 PersistenceType
UpgradeStorageHelperBase::PersistenceTypeFromSpec(
10690 const nsCString
& aSpec
) {
10691 // There's no moving of origin directories between repositories like in the
10692 // CreateOrUpgradeDirectoryMetadataHelper
10693 return *mPersistenceType
;
10696 nsresult
UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
10697 OriginProps
& aOriginProps
, bool* aRemoved
) {
10698 AssertIsOnIOThread();
10699 MOZ_ASSERT(aRemoved
);
10704 Nullable
<bool> isApp
;
10705 nsresult rv
= GetDirectoryMetadata(aOriginProps
.mDirectory
.get(), timestamp
,
10706 group
, origin
, isApp
);
10707 if (NS_FAILED(rv
) || isApp
.IsNull()) {
10708 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
10709 aOriginProps
.mNeedsRestore
= true;
10711 aOriginProps
.mTimestamp
= timestamp
;
10718 nsresult
UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
10719 const OriginProps
& aOriginProps
) {
10720 AssertIsOnIOThread();
10722 // This handles changes in origin string generation from nsIPrincipal,
10723 // especially the change from: appId+inMozBrowser+originNoSuffix
10724 // to: origin (with origin suffix).
10725 QM_TRY_INSPECT(const bool& renamed
, MaybeRenameOrigin(aOriginProps
));
10730 if (aOriginProps
.mNeedsRestore
) {
10731 QM_TRY(CreateDirectoryMetadata(*aOriginProps
.mDirectory
,
10732 aOriginProps
.mTimestamp
,
10733 aOriginProps
.mOriginMetadata
));
10736 QM_TRY(CreateDirectoryMetadata2(
10737 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
10738 /* aPersisted */ false, aOriginProps
.mOriginMetadata
));
10743 nsresult
UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
10744 const OriginProps
& aOriginProps
) {
10745 AssertIsOnIOThread();
10747 // The Cache API was creating top level morgue directories by accident for
10748 // a short time in nightly. This unfortunately prevents all storage from
10749 // working. So recover these profiles permanently by removing these corrupt
10750 // directories as part of this upgrade.
10752 QM_TRY_INSPECT(const auto& morgueDir
,
10753 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<nsIFile
>,
10754 *aOriginProps
.mDirectory
, Clone
));
10756 QM_TRY(morgueDir
->Append(u
"morgue"_ns
));
10758 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE(morgueDir
, Exists
));
10761 QM_WARNING("Deleting accidental morgue directory!");
10763 QM_TRY(morgueDir
->Remove(/* recursive */ true));
10769 Result
<bool, nsresult
> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
10770 const OriginProps
& aOriginProps
) {
10771 AssertIsOnIOThread();
10773 // TODO: This method was empty for some time due to accidental changes done
10774 // in bug 1320404. This led to renaming of origin directories like:
10775 // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
10777 // https+++developer.cdn.mozilla.net^inBrowser=1
10778 // instead of just removing them.
10780 const nsCString
& originalSuffix
= aOriginProps
.mOriginalSuffix
;
10781 if (!originalSuffix
.IsEmpty()) {
10782 MOZ_ASSERT(originalSuffix
[0] == '^');
10784 if (!URLParams::Parse(
10785 Substring(originalSuffix
, 1, originalSuffix
.Length() - 1),
10786 [](const nsAString
& aName
, const nsAString
& aValue
) {
10787 if (aName
.EqualsLiteral("appId")) {
10793 QM_TRY(RemoveObsoleteOrigin(aOriginProps
));
10802 nsresult
UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
10803 OriginProps
& aOriginProps
, bool* aRemoved
) {
10804 AssertIsOnIOThread();
10805 MOZ_ASSERT(aRemoved
);
10807 QM_TRY(MaybeRemoveMorgueDirectory(aOriginProps
));
10810 MaybeUpgradeClients(aOriginProps
, &Client::UpgradeStorageFrom1_0To2_0
));
10812 QM_TRY_INSPECT(const bool& removed
, MaybeRemoveAppsData(aOriginProps
));
10821 Nullable
<bool> isApp
;
10822 nsresult rv
= GetDirectoryMetadata(aOriginProps
.mDirectory
.get(), timestamp
,
10823 group
, origin
, isApp
);
10824 if (NS_FAILED(rv
) || isApp
.IsNull()) {
10825 aOriginProps
.mNeedsRestore
= true;
10829 rv
= GetDirectoryMetadata2(aOriginProps
.mDirectory
.get(), timestamp
, suffix
,
10830 group
, origin
, isApp
.SetValue());
10831 if (NS_FAILED(rv
)) {
10832 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
10833 aOriginProps
.mNeedsRestore2
= true;
10835 aOriginProps
.mTimestamp
= timestamp
;
10842 nsresult
UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
10843 const OriginProps
& aOriginProps
) {
10844 AssertIsOnIOThread();
10846 // This handles changes in origin string generation from nsIPrincipal,
10847 // especially the stripping of obsolete origin attributes like addonId.
10848 QM_TRY_INSPECT(const bool& renamed
, MaybeRenameOrigin(aOriginProps
));
10853 if (aOriginProps
.mNeedsRestore
) {
10854 QM_TRY(CreateDirectoryMetadata(*aOriginProps
.mDirectory
,
10855 aOriginProps
.mTimestamp
,
10856 aOriginProps
.mOriginMetadata
));
10859 if (aOriginProps
.mNeedsRestore2
) {
10860 QM_TRY(CreateDirectoryMetadata2(
10861 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
10862 /* aPersisted */ false, aOriginProps
.mOriginMetadata
));
10868 nsresult
UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
10869 OriginProps
& aOriginProps
, bool* aRemoved
) {
10870 AssertIsOnIOThread();
10871 MOZ_ASSERT(aRemoved
);
10874 MaybeUpgradeClients(aOriginProps
, &Client::UpgradeStorageFrom2_0To2_1
));
10879 Nullable
<bool> isApp
;
10880 nsresult rv
= GetDirectoryMetadata(aOriginProps
.mDirectory
.get(), timestamp
,
10881 group
, origin
, isApp
);
10882 if (NS_FAILED(rv
) || isApp
.IsNull()) {
10883 aOriginProps
.mNeedsRestore
= true;
10887 rv
= GetDirectoryMetadata2(aOriginProps
.mDirectory
.get(), timestamp
, suffix
,
10888 group
, origin
, isApp
.SetValue());
10889 if (NS_FAILED(rv
)) {
10890 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
10891 aOriginProps
.mNeedsRestore2
= true;
10893 aOriginProps
.mTimestamp
= timestamp
;
10900 nsresult
UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
10901 const OriginProps
& aOriginProps
) {
10902 AssertIsOnIOThread();
10904 if (aOriginProps
.mNeedsRestore
) {
10905 QM_TRY(CreateDirectoryMetadata(*aOriginProps
.mDirectory
,
10906 aOriginProps
.mTimestamp
,
10907 aOriginProps
.mOriginMetadata
));
10910 if (aOriginProps
.mNeedsRestore2
) {
10911 QM_TRY(CreateDirectoryMetadata2(
10912 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
10913 /* aPersisted */ false, aOriginProps
.mOriginMetadata
));
10919 nsresult
UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
10920 OriginProps
& aOriginProps
, bool* aRemoved
) {
10921 AssertIsOnIOThread();
10922 MOZ_ASSERT(aRemoved
);
10925 MaybeUpgradeClients(aOriginProps
, &Client::UpgradeStorageFrom2_1To2_2
));
10930 Nullable
<bool> isApp
;
10931 nsresult rv
= GetDirectoryMetadata(aOriginProps
.mDirectory
.get(), timestamp
,
10932 group
, origin
, isApp
);
10933 if (NS_FAILED(rv
) || isApp
.IsNull()) {
10934 aOriginProps
.mNeedsRestore
= true;
10938 rv
= GetDirectoryMetadata2(aOriginProps
.mDirectory
.get(), timestamp
, suffix
,
10939 group
, origin
, isApp
.SetValue());
10940 if (NS_FAILED(rv
)) {
10941 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
10942 aOriginProps
.mNeedsRestore2
= true;
10944 aOriginProps
.mTimestamp
= timestamp
;
10951 nsresult
UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
10952 const OriginProps
& aOriginProps
) {
10953 AssertIsOnIOThread();
10955 if (aOriginProps
.mNeedsRestore
) {
10956 QM_TRY(CreateDirectoryMetadata(*aOriginProps
.mDirectory
,
10957 aOriginProps
.mTimestamp
,
10958 aOriginProps
.mOriginMetadata
));
10961 if (aOriginProps
.mNeedsRestore2
) {
10962 QM_TRY(CreateDirectoryMetadata2(
10963 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
10964 /* aPersisted */ false, aOriginProps
.mOriginMetadata
));
10970 nsresult
UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
10971 nsIFile
* aFile
, const nsAString
& aLeafName
, bool& aRemoved
) {
10972 AssertIsOnIOThread();
10974 if (Client::IsDeprecatedClient(aLeafName
)) {
10975 QM_WARNING("Deleting deprecated %s client!",
10976 NS_ConvertUTF16toUTF8(aLeafName
).get());
10978 QM_TRY(aFile
->Remove(true));
10988 nsresult
RestoreDirectoryMetadata2Helper::Init() {
10989 AssertIsOnIOThread();
10990 MOZ_ASSERT(mDirectory
);
10992 nsCOMPtr
<nsIFile
> parentDir
;
10993 QM_TRY(mDirectory
->GetParent(getter_AddRefs(parentDir
)));
10995 const auto maybePersistenceType
=
10996 PersistenceTypeFromFile(*parentDir
, fallible
);
10997 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
10999 mPersistenceType
.init(maybePersistenceType
.value());
11004 nsresult
RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
11005 OriginProps
originProps(WrapMovingNotNull(mDirectory
));
11006 QM_TRY(originProps
.Init(
11007 [&self
= *this](const auto& aSpec
) { return *self
.mPersistenceType
; }));
11009 QM_TRY(OkIf(originProps
.mType
!= OriginProps::eInvalid
), NS_ERROR_FAILURE
);
11011 originProps
.mTimestamp
= GetOriginLastModifiedTime(originProps
);
11013 mOriginProps
.AppendElement(std::move(originProps
));
11015 QM_TRY(ProcessOriginDirectories());
11020 nsresult
RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
11021 const OriginProps
& aOriginProps
) {
11022 AssertIsOnIOThread();
11024 // We don't have any approach to restore aPersisted, so reset it to false.
11025 QM_TRY(CreateDirectoryMetadata2(
11026 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
11027 /* aPersisted */ false, aOriginProps
.mOriginMetadata
));
11032 } // namespace mozilla::dom::quota