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