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