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/LocalStorageCommon.h"
86 #include "mozilla/dom/StorageDBUpdater.h"
87 #include "mozilla/dom/cache/QuotaClient.h"
88 #include "mozilla/dom/indexedDB/ActorsParent.h"
89 #include "mozilla/dom/ipc/IdType.h"
90 #include "mozilla/dom/localstorage/ActorsParent.h"
91 #include "mozilla/dom/quota/AssertionsImpl.h"
92 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
93 #include "mozilla/dom/quota/Client.h"
94 #include "mozilla/dom/quota/Config.h"
95 #include "mozilla/dom/quota/Constants.h"
96 #include "mozilla/dom/quota/DirectoryLock.h"
97 #include "mozilla/dom/quota/FileUtils.h"
98 #include "mozilla/dom/quota/PersistenceType.h"
99 #include "mozilla/dom/quota/QuotaManagerService.h"
100 #include "mozilla/dom/quota/ResultExtensions.h"
101 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
102 #include "mozilla/dom/quota/StreamUtils.h"
103 #include "mozilla/dom/simpledb/ActorsParent.h"
104 #include "mozilla/fallible.h"
105 #include "mozilla/ipc/BackgroundChild.h"
106 #include "mozilla/ipc/BackgroundParent.h"
107 #include "mozilla/ipc/PBackgroundChild.h"
108 #include "mozilla/ipc/PBackgroundSharedTypes.h"
109 #include "mozilla/ipc/ProtocolUtils.h"
110 #include "mozilla/net/ExtensionProtocolHandler.h"
111 #include "nsAppDirectoryServiceDefs.h"
112 #include "nsBaseHashtable.h"
113 #include "nsCOMPtr.h"
114 #include "nsCRTGlue.h"
115 #include "nsClassHashtable.h"
116 #include "nsComponentManagerUtils.h"
117 #include "nsContentUtils.h"
119 #include "nsDirectoryServiceUtils.h"
121 #include "nsIBinaryInputStream.h"
122 #include "nsIBinaryOutputStream.h"
123 #include "nsIConsoleService.h"
124 #include "nsIDirectoryEnumerator.h"
125 #include "nsIDUtils.h"
126 #include "nsIEventTarget.h"
128 #include "nsIFileStreams.h"
129 #include "nsIInputStream.h"
130 #include "nsIObjectInputStream.h"
131 #include "nsIObjectOutputStream.h"
132 #include "nsIObserver.h"
133 #include "nsIObserverService.h"
134 #include "nsIOutputStream.h"
135 #include "nsIQuotaRequests.h"
136 #include "nsIPlatformInfo.h"
137 #include "nsIPrincipal.h"
138 #include "nsIRunnable.h"
139 #include "nsIScriptObjectPrincipal.h"
140 #include "nsISupports.h"
141 #include "nsISupportsPrimitives.h"
142 #include "nsIThread.h"
143 #include "nsITimer.h"
145 #include "nsIWidget.h"
146 #include "nsLiteralString.h"
147 #include "nsNetUtil.h"
148 #include "nsPIDOMWindow.h"
149 #include "nsPrintfCString.h"
150 #include "nsStandardURL.h"
151 #include "nsServiceManagerUtils.h"
152 #include "nsString.h"
153 #include "nsStringFlags.h"
154 #include "nsStringFwd.h"
155 #include "nsTArray.h"
156 #include "nsTHashtable.h"
157 #include "nsTLiteralString.h"
158 #include "nsTPromiseFlatString.h"
159 #include "nsTStringRepr.h"
160 #include "nsThreadUtils.h"
161 #include "nsURLHelper.h"
163 #include "nsXPCOMCID.h"
164 #include "nsXULAppAPI.h"
165 #include "prinrval.h"
169 // The amount of time, in milliseconds, that our IO thread will stay alive
170 // after the last event it processes.
171 #define DEFAULT_THREAD_TIMEOUT_MS 30000
174 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
177 #define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
180 * Automatically crash the browser if shutdown of a quota client takes this
181 * long. We've chosen a value that is long enough that it is unlikely for the
182 * problem to be falsely triggered by slow system I/O. We've also chosen a
183 * value long enough so that automated tests should time out and fail if
184 * shutdown of a quota client takes too long. Also, this value is long enough
185 * so that testers can notice the timeout; we want to know about the timeouts,
186 * not hide them. On the other hand this value is less than 60 seconds which is
187 * used by nsTerminator to crash a hung main process.
189 #define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
192 SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS
> SHUTDOWN_KILL_ACTORS_TIMEOUT_MS
,
193 "The kill actors timeout must be shorter than the crash browser one.");
195 // profile-before-change, when we need to shut down quota manager
196 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
199 #define MB *1024ULL KB
200 #define GB *1024ULL MB
202 namespace mozilla::dom::quota
{
204 using namespace mozilla::ipc
;
206 // We want profiles to be platform-independent so we always need to replace
207 // the same characters on every platform. Windows has the most extensive set
208 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
209 // FILE_PATH_SEPARATOR.
210 const char QuotaManager::kReplaceChars
[] = CONTROL_CHARACTERS
"/:*?\"<>|\\";
211 const char16_t
QuotaManager::kReplaceChars16
[] =
212 u
"" CONTROL_CHARACTERS
"/:*?\"<>|\\";
216 /*******************************************************************************
218 ******************************************************************************/
220 const uint32_t kSQLitePageSizeOverride
= 512;
222 // Important version history:
223 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
224 // which caused Firefox 57 release concerns because the major schema upgrade
225 // means anyone downgrading to Firefox 56 will experience a non-operational
226 // QuotaManager and all of its clients.
227 // - Bug 1404344 got very concerned about that and so we decided to effectively
228 // rename 3.0 to 2.1, effective in Firefox 57. This works because post
229 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
230 // increases. It also works because all the upgrade did was give the DOM
231 // Cache API QuotaClient an opportunity to create its newly added .padding
232 // files during initialization/upgrade, which isn't functionally necessary as
233 // that can be done on demand.
235 // Major storage version. Bump for backwards-incompatible changes.
236 // (The next major version should be 4 to distinguish from the Bug 1290481
238 const uint32_t kMajorStorageVersion
= 2;
240 // Minor storage version. Bump for backwards-compatible changes.
241 const uint32_t kMinorStorageVersion
= 3;
243 // The storage version we store in the SQLite database is a (signed) 32-bit
244 // integer. The major version is left-shifted 16 bits so the max value is
245 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
246 static_assert(kMajorStorageVersion
<= 0xFFFF,
247 "Major version needs to fit in 16 bits.");
248 static_assert(kMinorStorageVersion
<= 0xFFFF,
249 "Minor version needs to fit in 16 bits.");
251 const int32_t kStorageVersion
=
252 int32_t((kMajorStorageVersion
<< 16) + kMinorStorageVersion
);
254 // See comments above about why these are a thing.
255 const int32_t kHackyPreDowngradeStorageVersion
= int32_t((3 << 16) + 0);
256 const int32_t kHackyPostDowngradeStorageVersion
= int32_t((2 << 16) + 1);
258 const char kAboutHomeOriginPrefix
[] = "moz-safe-about:home";
259 const char kIndexedDBOriginPrefix
[] = "indexeddb://";
260 const char kResourceOriginPrefix
[] = "resource://";
262 constexpr auto kStorageName
= u
"storage"_ns
;
264 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
265 #define ARCHIVES_DIRECTORY_NAME u"archives"
266 #define PERSISTENT_DIRECTORY_NAME u"persistent"
267 #define PERMANENT_DIRECTORY_NAME u"permanent"
268 #define TEMPORARY_DIRECTORY_NAME u"temporary"
269 #define DEFAULT_DIRECTORY_NAME u"default"
270 #define PRIVATE_DIRECTORY_NAME u"private"
271 #define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed"
273 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
274 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
275 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
277 const int32_t kLocalStorageArchiveVersion
= 4;
279 const char kProfileDoChangeTopic
[] = "profile-do-change";
280 const char kPrivateBrowsingObserverTopic
[] = "last-pb-context-exited";
282 const int32_t kCacheVersion
= 2;
284 /******************************************************************************
286 ******************************************************************************/
288 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion
,
289 uint32_t aMinorStorageVersion
) {
290 return int32_t((aMajorStorageVersion
<< 16) + aMinorStorageVersion
);
293 uint32_t GetMajorStorageVersion(int32_t aStorageVersion
) {
294 return uint32_t(aStorageVersion
>> 16);
297 nsresult
CreateTables(mozIStorageConnection
* aConnection
) {
298 AssertIsOnIOThread();
299 MOZ_ASSERT(aConnection
);
302 QM_TRY(MOZ_TO_RESULT(
303 aConnection
->ExecuteSimpleSQL("CREATE TABLE database"
304 "( cache_version INTEGER NOT NULL DEFAULT 0"
309 QM_TRY_INSPECT(const int32_t& storageVersion
,
310 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection
, GetSchemaVersion
));
312 MOZ_ASSERT(storageVersion
== 0);
316 QM_TRY(MOZ_TO_RESULT(aConnection
->SetSchemaVersion(kStorageVersion
)));
321 Result
<int32_t, nsresult
> LoadCacheVersion(mozIStorageConnection
& aConnection
) {
322 AssertIsOnIOThread();
324 QM_TRY_INSPECT(const auto& stmt
,
325 CreateAndExecuteSingleStepStatement
<
326 SingleStepResult::ReturnNullIfNoResult
>(
327 aConnection
, "SELECT cache_version FROM database"_ns
));
329 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FILE_CORRUPTED
));
331 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt32
, 0));
334 nsresult
SaveCacheVersion(mozIStorageConnection
& aConnection
,
336 AssertIsOnIOThread();
340 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
341 nsCOMPtr
<mozIStorageStatement
>, aConnection
, CreateStatement
,
342 "UPDATE database SET cache_version = :version;"_ns
));
344 QM_TRY(MOZ_TO_RESULT(stmt
->BindInt32ByName("version"_ns
, aVersion
)));
346 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
351 nsresult
CreateCacheTables(mozIStorageConnection
& aConnection
) {
352 AssertIsOnIOThread();
355 QM_TRY(MOZ_TO_RESULT(
356 aConnection
.ExecuteSimpleSQL("CREATE TABLE cache"
357 "( valid INTEGER NOT NULL DEFAULT 0"
358 ", build_id TEXT NOT NULL DEFAULT ''"
361 // Table `repository`
363 MOZ_TO_RESULT(aConnection
.ExecuteSimpleSQL("CREATE TABLE repository"
364 "( id INTEGER PRIMARY KEY"
365 ", name TEXT NOT NULL"
369 QM_TRY(MOZ_TO_RESULT(
370 aConnection
.ExecuteSimpleSQL("CREATE TABLE origin"
371 "( repository_id INTEGER NOT NULL"
373 ", group_ TEXT NOT NULL"
374 ", origin TEXT NOT NULL"
375 ", client_usages TEXT NOT NULL"
376 ", usage INTEGER NOT NULL"
377 ", last_access_time INTEGER NOT NULL"
378 ", accessed INTEGER NOT NULL"
379 ", persisted INTEGER NOT NULL"
380 ", PRIMARY KEY (repository_id, origin)"
381 ", FOREIGN KEY (repository_id) "
382 "REFERENCES repository(id) "
387 QM_TRY_INSPECT(const int32_t& cacheVersion
, LoadCacheVersion(aConnection
));
388 MOZ_ASSERT(cacheVersion
== 0);
392 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection
, kCacheVersion
)));
397 OkOrErr
InvalidateCache(mozIStorageConnection
& aConnection
) {
398 AssertIsOnIOThread();
400 static constexpr auto kDeleteCacheQuery
= "DELETE FROM origin;"_ns
;
401 static constexpr auto kSetInvalidFlagQuery
= "UPDATE cache SET valid = 0"_ns
;
403 QM_TRY(QM_OR_ELSE_WARN(
406 mozStorageTransaction
transaction(&aConnection
,
407 /*aCommitOnComplete */ false);
409 QM_TRY(QM_TO_RESULT(transaction
.Start()));
410 QM_TRY(QM_TO_RESULT(aConnection
.ExecuteSimpleSQL(kDeleteCacheQuery
)));
412 QM_TO_RESULT(aConnection
.ExecuteSimpleSQL(kSetInvalidFlagQuery
)));
413 QM_TRY(QM_TO_RESULT(transaction
.Commit()));
418 ([&](const QMResult
& rv
) -> OkOrErr
{
420 QM_TO_RESULT(aConnection
.ExecuteSimpleSQL(kSetInvalidFlagQuery
)));
428 nsresult
UpgradeCacheFrom1To2(mozIStorageConnection
& aConnection
) {
429 AssertIsOnIOThread();
431 QM_TRY(MOZ_TO_RESULT(aConnection
.ExecuteSimpleSQL(
432 "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns
)));
434 QM_TRY(InvalidateCache(aConnection
));
438 QM_TRY_INSPECT(const int32_t& cacheVersion
, LoadCacheVersion(aConnection
));
440 MOZ_ASSERT(cacheVersion
== 1);
444 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection
, 2)));
449 Result
<bool, nsresult
> MaybeCreateOrUpgradeCache(
450 mozIStorageConnection
& aConnection
) {
451 bool cacheUsable
= true;
453 QM_TRY_UNWRAP(int32_t cacheVersion
, LoadCacheVersion(aConnection
));
455 if (cacheVersion
> kCacheVersion
) {
457 } else if (cacheVersion
!= kCacheVersion
) {
458 const bool newCache
= !cacheVersion
;
460 mozStorageTransaction
transaction(
461 &aConnection
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
463 QM_TRY(MOZ_TO_RESULT(transaction
.Start()));
466 QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection
)));
470 QM_TRY_INSPECT(const int32_t& cacheVersion
,
471 LoadCacheVersion(aConnection
));
472 MOZ_ASSERT(cacheVersion
== kCacheVersion
);
476 QM_TRY(MOZ_TO_RESULT(aConnection
.ExecuteSimpleSQL(
477 nsLiteralCString("INSERT INTO cache (valid, build_id) "
478 "VALUES (0, '')"))));
480 nsCOMPtr
<mozIStorageStatement
> insertStmt
;
482 for (const PersistenceType persistenceType
: kAllPersistenceTypes
) {
484 MOZ_ALWAYS_SUCCEEDS(insertStmt
->Reset());
486 QM_TRY_UNWRAP(insertStmt
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
487 nsCOMPtr
<mozIStorageStatement
>,
488 aConnection
, CreateStatement
,
489 "INSERT INTO repository (id, name) "
490 "VALUES (:id, :name)"_ns
));
493 QM_TRY(MOZ_TO_RESULT(
494 insertStmt
->BindInt32ByName("id"_ns
, persistenceType
)));
496 QM_TRY(MOZ_TO_RESULT(insertStmt
->BindUTF8StringByName(
497 "name"_ns
, PersistenceTypeToString(persistenceType
))));
499 QM_TRY(MOZ_TO_RESULT(insertStmt
->Execute()));
502 // This logic needs to change next time we change the cache!
503 static_assert(kCacheVersion
== 2,
504 "Upgrade function needed due to cache version increase.");
506 while (cacheVersion
!= kCacheVersion
) {
507 if (cacheVersion
== 1) {
508 QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection
)));
510 QM_FAIL(Err(NS_ERROR_FAILURE
), []() {
512 "Unable to initialize cache, no upgrade path is "
517 QM_TRY_UNWRAP(cacheVersion
, LoadCacheVersion(aConnection
));
520 MOZ_ASSERT(cacheVersion
== kCacheVersion
);
523 QM_TRY(MOZ_TO_RESULT(transaction
.Commit()));
529 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
> CreateWebAppsStoreConnection(
530 nsIFile
& aWebAppsStoreFile
, mozIStorageService
& aStorageService
) {
531 AssertIsOnIOThread();
533 // Check if the old database exists at all.
534 QM_TRY_INSPECT(const bool& exists
,
535 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile
, Exists
));
538 // webappsstore.sqlite doesn't exist, return a null connection.
539 return nsCOMPtr
<mozIStorageConnection
>{};
542 QM_TRY_INSPECT(const bool& isDirectory
,
543 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile
, IsDirectory
));
546 QM_WARNING("webappsstore.sqlite is not a file!");
547 return nsCOMPtr
<mozIStorageConnection
>{};
550 QM_TRY_INSPECT(const auto& connection
,
553 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
554 nsCOMPtr
<mozIStorageConnection
>, aStorageService
,
555 OpenUnsharedDatabase
, &aWebAppsStoreFile
,
556 mozIStorageService::CONNECTION_DEFAULT
),
558 IsDatabaseCorruptionError
,
559 // Fallback. Don't throw an error, leave a corrupted
560 // webappsstore database as it is.
561 ErrToDefaultOk
<nsCOMPtr
<mozIStorageConnection
>>));
564 // Don't propagate an error, leave a non-updateable webappsstore database as
566 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection
)),
567 nsCOMPtr
<mozIStorageConnection
>{});
573 Result
<nsCOMPtr
<nsIFile
>, QMResult
> GetLocalStorageArchiveFile(
574 const nsAString
& aDirectoryPath
) {
575 AssertIsOnIOThread();
576 MOZ_ASSERT(!aDirectoryPath
.IsEmpty());
578 QM_TRY_UNWRAP(auto lsArchiveFile
,
579 QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath
)));
582 lsArchiveFile
->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME
))));
584 return lsArchiveFile
;
587 Result
<nsCOMPtr
<nsIFile
>, nsresult
> GetLocalStorageArchiveTmpFile(
588 const nsAString
& aDirectoryPath
) {
589 AssertIsOnIOThread();
590 MOZ_ASSERT(!aDirectoryPath
.IsEmpty());
592 QM_TRY_UNWRAP(auto lsArchiveTmpFile
, QM_NewLocalFile(aDirectoryPath
));
594 QM_TRY(MOZ_TO_RESULT(
595 lsArchiveTmpFile
->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME
))));
597 return lsArchiveTmpFile
;
600 Result
<bool, nsresult
> IsLocalStorageArchiveInitialized(
601 mozIStorageConnection
& aConnection
) {
602 AssertIsOnIOThread();
605 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection
, TableExists
, "database"_ns
));
608 nsresult
InitializeLocalStorageArchive(mozIStorageConnection
* aConnection
) {
609 AssertIsOnIOThread();
610 MOZ_ASSERT(aConnection
);
614 QM_TRY_INSPECT(const auto& initialized
,
615 IsLocalStorageArchiveInitialized(*aConnection
));
616 MOZ_ASSERT(!initialized
);
620 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteSimpleSQL(
621 "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns
)));
625 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
626 nsCOMPtr
<mozIStorageStatement
>, aConnection
, CreateStatement
,
627 "INSERT INTO database (version) VALUES (:version)"_ns
));
629 QM_TRY(MOZ_TO_RESULT(stmt
->BindInt32ByName("version"_ns
, 0)));
630 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
635 Result
<int32_t, nsresult
> LoadLocalStorageArchiveVersion(
636 mozIStorageConnection
& aConnection
) {
637 AssertIsOnIOThread();
639 QM_TRY_INSPECT(const auto& stmt
,
640 CreateAndExecuteSingleStepStatement
<
641 SingleStepResult::ReturnNullIfNoResult
>(
642 aConnection
, "SELECT version FROM database"_ns
));
644 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FILE_CORRUPTED
));
646 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt32
, 0));
649 nsresult
SaveLocalStorageArchiveVersion(mozIStorageConnection
* aConnection
,
651 AssertIsOnIOThread();
652 MOZ_ASSERT(aConnection
);
654 nsCOMPtr
<mozIStorageStatement
> stmt
;
655 nsresult rv
= aConnection
->CreateStatement(
656 "UPDATE database SET version = :version;"_ns
, getter_AddRefs(stmt
));
657 if (NS_WARN_IF(NS_FAILED(rv
))) {
661 rv
= stmt
->BindInt32ByName("version"_ns
, aVersion
);
662 if (NS_WARN_IF(NS_FAILED(rv
))) {
666 rv
= stmt
->Execute();
667 if (NS_WARN_IF(NS_FAILED(rv
))) {
674 template <typename FileFunc
, typename DirectoryFunc
>
675 Result
<mozilla::Ok
, nsresult
> CollectEachFileEntry(
676 nsIFile
& aDirectory
, const FileFunc
& aFileFunc
,
677 const DirectoryFunc
& aDirectoryFunc
) {
678 AssertIsOnIOThread();
680 return CollectEachFile(
682 [&aFileFunc
, &aDirectoryFunc
](
683 const nsCOMPtr
<nsIFile
>& file
) -> Result
<mozilla::Ok
, nsresult
> {
684 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*file
));
686 switch (dirEntryKind
) {
687 case nsIFileKind::ExistsAsDirectory
:
688 return aDirectoryFunc(file
);
690 case nsIFileKind::ExistsAsFile
:
691 return aFileFunc(file
);
693 case nsIFileKind::DoesNotExist
:
694 // Ignore files that got removed externally while iterating.
702 /******************************************************************************
703 * Quota manager class declarations
704 ******************************************************************************/
708 class QuotaManager::Observer final
: public nsIObserver
{
709 static Observer
* sInstance
;
711 bool mPendingProfileChange
;
712 bool mShutdownComplete
;
715 static nsresult
Initialize();
717 static nsIObserver
* GetInstance();
719 static void ShutdownCompleted();
722 Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
723 MOZ_ASSERT(NS_IsMainThread());
726 ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
738 /*******************************************************************************
739 * Local class declarations
740 ******************************************************************************/
746 class CollectOriginsHelper final
: public Runnable
{
747 uint64_t mMinSizeToBeFreed
;
752 // The members below are protected by mMutex.
753 nsTArray
<RefPtr
<OriginDirectoryLock
>> mLocks
;
754 uint64_t mSizeToBeFreed
;
758 CollectOriginsHelper(mozilla::Mutex
& aMutex
, uint64_t aMinSizeToBeFreed
);
760 // Blocks the current thread until origins are collected on the main thread.
761 // The returned value contains an aggregate size of those origins.
762 int64_t BlockAndReturnOriginsForEviction(
763 nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
);
766 ~CollectOriginsHelper() = default;
772 /*******************************************************************************
773 * Other class declarations
774 ******************************************************************************/
776 class StoragePressureRunnable final
: public Runnable
{
777 const uint64_t mUsage
;
780 explicit StoragePressureRunnable(uint64_t aUsage
)
781 : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
785 ~StoragePressureRunnable() = default;
790 class RecordTimeDeltaHelper final
: public Runnable
{
791 const Telemetry::HistogramID mHistogram
;
793 // TimeStamps that are set on the IO thread.
794 LazyInitializedOnceNotNull
<const TimeStamp
> mStartTime
;
795 LazyInitializedOnceNotNull
<const TimeStamp
> mEndTime
;
797 // A TimeStamp that is set on the main thread.
798 LazyInitializedOnceNotNull
<const TimeStamp
> mInitializedTime
;
801 explicit RecordTimeDeltaHelper(const Telemetry::HistogramID aHistogram
)
802 : Runnable("dom::quota::RecordTimeDeltaHelper"), mHistogram(aHistogram
) {}
809 ~RecordTimeDeltaHelper() = default;
814 /*******************************************************************************
816 ******************************************************************************/
818 /*******************************************************************************
820 ******************************************************************************/
822 // Return whether the group was actually updated.
823 Result
<bool, nsresult
> MaybeUpdateGroupForOrigin(
824 OriginMetadata
& aOriginMetadata
) {
825 MOZ_ASSERT(!NS_IsMainThread());
827 bool updated
= false;
829 if (aOriginMetadata
.mOrigin
.EqualsLiteral(kChromeOrigin
)) {
830 if (!aOriginMetadata
.mGroup
.EqualsLiteral(kChromeOrigin
)) {
831 aOriginMetadata
.mGroup
.AssignLiteral(kChromeOrigin
);
835 nsCOMPtr
<nsIPrincipal
> principal
=
836 BasePrincipal::CreateContentPrincipal(aOriginMetadata
.mOrigin
);
837 QM_TRY(MOZ_TO_RESULT(principal
));
839 QM_TRY_INSPECT(const auto& baseDomain
,
840 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString
, principal
,
843 const nsCString upToDateGroup
= baseDomain
+ aOriginMetadata
.mSuffix
;
845 if (aOriginMetadata
.mGroup
!= upToDateGroup
) {
846 aOriginMetadata
.mGroup
= upToDateGroup
;
854 Result
<bool, nsresult
> MaybeUpdateLastAccessTimeForOrigin(
855 FullOriginMetadata
& aFullOriginMetadata
) {
856 MOZ_ASSERT(!NS_IsMainThread());
858 if (aFullOriginMetadata
.mLastAccessTime
== INT64_MIN
) {
859 QuotaManager
* quotaManager
= QuotaManager::Get();
860 MOZ_ASSERT(quotaManager
);
862 QM_TRY_INSPECT(const auto& metadataFile
,
863 quotaManager
->GetOriginDirectory(aFullOriginMetadata
));
865 QM_TRY(MOZ_TO_RESULT(
866 metadataFile
->Append(nsLiteralString(METADATA_V2_FILE_NAME
))));
868 QM_TRY_UNWRAP(int64_t timestamp
, MOZ_TO_RESULT_INVOKE_MEMBER(
869 metadataFile
, GetLastModifiedTime
));
871 // Need to convert from milliseconds to microseconds.
872 MOZ_ASSERT((INT64_MAX
/ PR_USEC_PER_MSEC
) > timestamp
);
873 timestamp
*= int64_t(PR_USEC_PER_MSEC
);
875 aFullOriginMetadata
.mLastAccessTime
= timestamp
;
885 BackgroundThreadObject::BackgroundThreadObject()
886 : mOwningThread(GetCurrentSerialEventTarget()) {
887 AssertIsOnOwningThread();
890 BackgroundThreadObject::BackgroundThreadObject(
891 nsISerialEventTarget
* aOwningThread
)
892 : mOwningThread(aOwningThread
) {}
896 void BackgroundThreadObject::AssertIsOnOwningThread() const {
897 AssertIsOnBackgroundThread();
898 MOZ_ASSERT(mOwningThread
);
900 MOZ_ASSERT(NS_SUCCEEDED(mOwningThread
->IsOnCurrentThread(¤t
)));
906 nsISerialEventTarget
* BackgroundThreadObject::OwningThread() const {
907 MOZ_ASSERT(mOwningThread
);
908 return mOwningThread
;
911 void ReportInternalError(const char* aFile
, uint32_t aLine
, const char* aStr
) {
912 // Get leaf of file path
913 for (const char* p
= aFile
; *p
; ++p
) {
914 if (*p
== '/' && *(p
+ 1)) {
919 nsContentUtils::LogSimpleConsoleError(
920 NS_ConvertUTF8toUTF16(
921 nsPrintfCString("Quota %s: %s:%" PRIu32
, aStr
, aFile
, aLine
)),
923 false /* Quota Manager is not active in private browsing mode */,
924 true /* Quota Manager runs always in a chrome context */);
929 bool gInvalidateQuotaCache
= false;
930 StaticAutoPtr
<nsString
> gBasePath
;
931 StaticAutoPtr
<nsString
> gStorageName
;
932 StaticAutoPtr
<nsCString
> gBuildId
;
935 bool gQuotaManagerInitialized
= false;
938 StaticRefPtr
<QuotaManager
> gInstance
;
939 mozilla::Atomic
<bool> gShutdown(false);
941 // A time stamp that can only be accessed on the main thread.
942 TimeStamp gLastOSWake
;
944 // XXX Move to QuotaManager once NormalOriginOperationBase is declared in a
945 // separate and includable file.
946 using NormalOriginOpArray
=
947 nsTArray
<CheckedUnsafePtr
<NormalOriginOperationBase
>>;
948 StaticAutoPtr
<NormalOriginOpArray
> gNormalOriginOps
;
950 class StorageOperationBase
{
953 enum Type
{ eChrome
, eContent
, eObsolete
, eInvalid
};
955 NotNull
<nsCOMPtr
<nsIFile
>> mDirectory
;
958 OriginAttributes mAttrs
;
960 OriginMetadata mOriginMetadata
;
961 nsCString mOriginalSuffix
;
963 LazyInitializedOnceEarlyDestructible
<const PersistenceType
>
971 explicit OriginProps(MovingNotNull
<nsCOMPtr
<nsIFile
>> aDirectory
)
972 : mDirectory(std::move(aDirectory
)),
975 mNeedsRestore(false),
976 mNeedsRestore2(false),
979 template <typename PersistenceTypeFunc
>
980 nsresult
Init(PersistenceTypeFunc
&& aPersistenceTypeFunc
);
983 nsTArray
<OriginProps
> mOriginProps
;
985 nsCOMPtr
<nsIFile
> mDirectory
;
988 explicit StorageOperationBase(nsIFile
* aDirectory
) : mDirectory(aDirectory
) {
989 AssertIsOnIOThread();
992 NS_INLINE_DECL_REFCOUNTING(StorageOperationBase
)
995 virtual ~StorageOperationBase() = default;
997 nsresult
GetDirectoryMetadata(nsIFile
* aDirectory
, int64_t& aTimestamp
,
998 nsACString
& aGroup
, nsACString
& aOrigin
,
999 Nullable
<bool>& aIsApp
);
1001 // Upgrade helper to load the contents of ".metadata-v2" files from previous
1002 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
1003 // method, it is only intended to read current version ".metadata-v2" files.
1004 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
1005 // because our "storage.sqlite" lets us track the overall version of the
1006 // storage directory.
1007 nsresult
GetDirectoryMetadata2(nsIFile
* aDirectory
, int64_t& aTimestamp
,
1008 nsACString
& aSuffix
, nsACString
& aGroup
,
1009 nsACString
& aOrigin
, bool& aIsApp
);
1011 int64_t GetOriginLastModifiedTime(const OriginProps
& aOriginProps
);
1013 nsresult
RemoveObsoleteOrigin(const OriginProps
& aOriginProps
);
1016 * Rename the origin if the origin string generation from nsIPrincipal
1017 * changed. This consists of renaming the origin in the metadata files and
1018 * renaming the origin directory itself. For simplicity, the origin in
1019 * metadata files is not actually updated, but the metadata files are
1020 * recreated instead.
1022 * @param aOriginProps the properties of the origin to check.
1024 * @return whether origin was renamed.
1026 Result
<bool, nsresult
> MaybeRenameOrigin(const OriginProps
& aOriginProps
);
1028 nsresult
ProcessOriginDirectories();
1030 virtual nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) = 0;
1033 class RepositoryOperationBase
: public StorageOperationBase
{
1035 explicit RepositoryOperationBase(nsIFile
* aDirectory
)
1036 : StorageOperationBase(aDirectory
) {}
1038 nsresult
ProcessRepository();
1041 virtual ~RepositoryOperationBase() = default;
1043 template <typename UpgradeMethod
>
1044 nsresult
MaybeUpgradeClients(const OriginProps
& aOriginsProps
,
1045 UpgradeMethod aMethod
);
1048 virtual PersistenceType
PersistenceTypeFromSpec(const nsCString
& aSpec
) = 0;
1050 virtual nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
1051 bool* aRemoved
) = 0;
1053 virtual nsresult
PrepareClientDirectory(nsIFile
* aFile
,
1054 const nsAString
& aLeafName
,
1058 class CreateOrUpgradeDirectoryMetadataHelper final
1059 : public RepositoryOperationBase
{
1060 nsCOMPtr
<nsIFile
> mPermanentStorageDir
;
1062 // The legacy PersistenceType, before the default repository introduction.
1063 enum class LegacyPersistenceType
{
1066 // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
1070 LazyInitializedOnce
<const LegacyPersistenceType
> mLegacyPersistenceType
;
1073 explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile
* aDirectory
)
1074 : RepositoryOperationBase(aDirectory
) {}
1079 Maybe
<LegacyPersistenceType
> LegacyPersistenceTypeFromFile(nsIFile
& aFile
,
1082 PersistenceType
PersistenceTypeFromLegacyPersistentSpec(
1083 const nsCString
& aSpec
);
1085 PersistenceType
PersistenceTypeFromSpec(const nsCString
& aSpec
) override
;
1087 nsresult
MaybeUpgradeOriginDirectory(nsIFile
* aDirectory
);
1089 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
1090 bool* aRemoved
) override
;
1092 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
1095 class UpgradeStorageHelperBase
: public RepositoryOperationBase
{
1096 LazyInitializedOnce
<const PersistenceType
> mPersistenceType
;
1099 explicit UpgradeStorageHelperBase(nsIFile
* aDirectory
)
1100 : RepositoryOperationBase(aDirectory
) {}
1105 PersistenceType
PersistenceTypeFromSpec(const nsCString
& aSpec
) override
;
1108 class UpgradeStorageFrom0_0To1_0Helper final
: public UpgradeStorageHelperBase
{
1110 explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile
* aDirectory
)
1111 : UpgradeStorageHelperBase(aDirectory
) {}
1114 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
1115 bool* aRemoved
) override
;
1117 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
1120 class UpgradeStorageFrom1_0To2_0Helper final
: public UpgradeStorageHelperBase
{
1122 explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile
* aDirectory
)
1123 : UpgradeStorageHelperBase(aDirectory
) {}
1126 nsresult
MaybeRemoveMorgueDirectory(const OriginProps
& aOriginProps
);
1129 * Remove the origin directory if appId is present in origin attributes.
1131 * @param aOriginProps the properties of the origin to check.
1133 * @return whether the origin directory was removed.
1135 Result
<bool, nsresult
> MaybeRemoveAppsData(const OriginProps
& aOriginProps
);
1137 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
1138 bool* aRemoved
) override
;
1140 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
1143 class UpgradeStorageFrom2_0To2_1Helper final
: public UpgradeStorageHelperBase
{
1145 explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile
* aDirectory
)
1146 : UpgradeStorageHelperBase(aDirectory
) {}
1149 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
1150 bool* aRemoved
) override
;
1152 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
1155 class UpgradeStorageFrom2_1To2_2Helper final
: public UpgradeStorageHelperBase
{
1157 explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile
* aDirectory
)
1158 : UpgradeStorageHelperBase(aDirectory
) {}
1161 nsresult
PrepareOriginDirectory(OriginProps
& aOriginProps
,
1162 bool* aRemoved
) override
;
1164 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
1166 nsresult
PrepareClientDirectory(nsIFile
* aFile
, const nsAString
& aLeafName
,
1167 bool& aRemoved
) override
;
1170 class RestoreDirectoryMetadata2Helper final
: public StorageOperationBase
{
1171 LazyInitializedOnce
<const PersistenceType
> mPersistenceType
;
1174 explicit RestoreDirectoryMetadata2Helper(nsIFile
* aDirectory
)
1175 : StorageOperationBase(aDirectory
) {}
1179 nsresult
RestoreMetadata2File();
1182 nsresult
ProcessOriginDirectory(const OriginProps
& aOriginProps
) override
;
1185 Result
<nsAutoString
, nsresult
> GetPathForStorage(
1186 nsIFile
& aBaseDir
, const nsAString
& aStorageName
) {
1187 QM_TRY_INSPECT(const auto& storageDir
,
1188 CloneFileAndAppend(aBaseDir
, aStorageName
));
1191 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString
, storageDir
, GetPath
));
1194 int64_t GetLastModifiedTime(PersistenceType aPersistenceType
, nsIFile
& aFile
) {
1195 AssertIsOnIOThread();
1197 class MOZ_STACK_CLASS Helper final
{
1199 static nsresult
GetLastModifiedTime(nsIFile
* aFile
, int64_t* aTimestamp
) {
1200 AssertIsOnIOThread();
1202 MOZ_ASSERT(aTimestamp
);
1204 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*aFile
));
1206 switch (dirEntryKind
) {
1207 case nsIFileKind::ExistsAsDirectory
:
1208 QM_TRY(CollectEachFile(
1210 [&aTimestamp
](const nsCOMPtr
<nsIFile
>& file
)
1211 -> Result
<mozilla::Ok
, nsresult
> {
1212 QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file
, aTimestamp
)));
1218 case nsIFileKind::ExistsAsFile
: {
1219 QM_TRY_INSPECT(const auto& leafName
,
1220 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString
, aFile
,
1223 // Bug 1595445 will handle unknown files here.
1225 if (IsOriginMetadata(leafName
) || IsTempMetadata(leafName
) ||
1226 IsDotFile(leafName
)) {
1230 QM_TRY_UNWRAP(int64_t timestamp
, MOZ_TO_RESULT_INVOKE_MEMBER(
1231 aFile
, GetLastModifiedTime
));
1233 // Need to convert from milliseconds to microseconds.
1234 MOZ_ASSERT((INT64_MAX
/ PR_USEC_PER_MSEC
) > timestamp
);
1235 timestamp
*= int64_t(PR_USEC_PER_MSEC
);
1237 if (timestamp
> *aTimestamp
) {
1238 *aTimestamp
= timestamp
;
1243 case nsIFileKind::DoesNotExist
:
1244 // Ignore files that got removed externally while iterating.
1252 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
1256 int64_t timestamp
= INT64_MIN
;
1257 nsresult rv
= Helper::GetLastModifiedTime(&aFile
, ×tamp
);
1258 if (NS_FAILED(rv
)) {
1259 timestamp
= PR_Now();
1262 // XXX if there were no suitable files for getting last modified time
1263 // (timestamp is still set to INT64_MIN), we should return the current time
1264 // instead of returning INT64_MIN.
1269 // Returns a bool indicating whether the directory was newly created.
1270 Result
<bool, nsresult
> EnsureDirectory(nsIFile
& aDirectory
) {
1271 AssertIsOnIOThread();
1273 // Callers call this function without checking if the directory already
1274 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
1275 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
1277 QM_TRY_INSPECT(const auto& exists
,
1278 QM_OR_ELSE_LOG_VERBOSE_IF(
1280 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory
, Create
,
1281 nsIFile::DIRECTORY_TYPE
, 0755,
1282 /* aSkipAncestors = */ false)
1283 .map([](Ok
) { return false; }),
1285 IsSpecificError
<NS_ERROR_FILE_ALREADY_EXISTS
>,
1290 QM_TRY_INSPECT(const bool& isDirectory
,
1291 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory
, IsDirectory
));
1292 QM_TRY(OkIf(isDirectory
), Err(NS_ERROR_UNEXPECTED
));
1298 void GetJarPrefix(bool aInIsolatedMozBrowser
, nsACString
& aJarPrefix
) {
1299 aJarPrefix
.Truncate();
1302 if (!aInIsolatedMozBrowser
) {
1306 // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
1308 // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
1309 aJarPrefix
.AppendInt(0); // TODO: this is the appId, to be removed.
1310 aJarPrefix
.Append('+');
1311 aJarPrefix
.Append(aInIsolatedMozBrowser
? 't' : 'f');
1312 aJarPrefix
.Append('+');
1315 // This method computes and returns our best guess for the temporary storage
1316 // limit (in bytes), based on disk capacity.
1317 Result
<uint64_t, nsresult
> GetTemporaryStorageLimit(nsIFile
& aStorageDir
) {
1318 // The fixed limit pref can be used to override temporary storage limit
1320 if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
1321 return static_cast<uint64_t>(
1322 StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
1326 constexpr int64_t teraByte
= (1024LL * 1024LL * 1024LL * 1024LL);
1327 constexpr int64_t maxAllowedCapacity
= 8LL * teraByte
;
1329 // Check for disk capacity of user's device on which storage directory lives.
1330 int64_t diskCapacity
= maxAllowedCapacity
;
1332 // Log error when default disk capacity is returned due to the error
1333 QM_WARNONLY_TRY(MOZ_TO_RESULT(aStorageDir
.GetDiskCapacity(&diskCapacity
)));
1335 MOZ_ASSERT(diskCapacity
>= 0LL);
1337 // Allow temporary storage to consume up to 50% of disk capacity.
1338 int64_t capacityLimit
= diskCapacity
/ 2LL;
1340 // If the disk capacity reported by the operating system is very
1341 // large and potentially incorrect due to hardware issues,
1342 // a hardcoded limit is supplied instead.
1344 OkIf(capacityLimit
< maxAllowedCapacity
),
1345 ([&capacityLimit
](const auto&) { capacityLimit
= maxAllowedCapacity
; }));
1347 return capacityLimit
;
1350 bool IsOriginUnaccessed(const FullOriginMetadata
& aFullOriginMetadata
,
1351 const int64_t aRecentTime
) {
1352 if (aFullOriginMetadata
.mLastAccessTime
> aRecentTime
) {
1356 return (aRecentTime
- aFullOriginMetadata
.mLastAccessTime
) / PR_USEC_PER_SEC
>
1357 StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec();
1362 /*******************************************************************************
1363 * Exported functions
1364 ******************************************************************************/
1366 void InitializeQuotaManager() {
1367 MOZ_ASSERT(XRE_IsParentProcess());
1368 MOZ_ASSERT(NS_IsMainThread());
1369 MOZ_ASSERT(!gQuotaManagerInitialized
);
1371 if (!QuotaManager::IsRunningGTests()) {
1372 // These services have to be started on the main thread currently.
1373 const nsCOMPtr
<mozIStorageService
> ss
=
1374 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID
);
1375 QM_WARNONLY_TRY(OkIf(ss
));
1377 RefPtr
<net::ExtensionProtocolHandler
> extensionProtocolHandler
=
1378 net::ExtensionProtocolHandler::GetSingleton();
1379 QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler
));
1382 QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));
1385 gQuotaManagerInitialized
= true;
1389 void InitializeScopedLogExtraInfo() {
1390 #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
1391 ScopedLogExtraInfo::Initialize();
1395 bool RecvShutdownQuotaManager() {
1396 AssertIsOnBackgroundThread();
1398 // If we are already in shutdown, don't call ShutdownInstance()
1399 // again and return true immediately. We shall see this incident
1401 // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124)
1402 // XXX todo: Active QM_TRY context for shutdown (Bug 1735170)
1403 QM_TRY(OkIf(!gShutdown
), true);
1405 QuotaManager::ShutdownInstance();
1410 QuotaManager::Observer
* QuotaManager::Observer::sInstance
= nullptr;
1413 nsresult
QuotaManager::Observer::Initialize() {
1414 MOZ_ASSERT(NS_IsMainThread());
1416 RefPtr
<Observer
> observer
= new Observer();
1418 nsresult rv
= observer
->Init();
1419 if (NS_WARN_IF(NS_FAILED(rv
))) {
1423 sInstance
= observer
;
1429 nsIObserver
* QuotaManager::Observer::GetInstance() {
1430 MOZ_ASSERT(NS_IsMainThread());
1436 void QuotaManager::Observer::ShutdownCompleted() {
1437 MOZ_ASSERT(NS_IsMainThread());
1438 MOZ_ASSERT(sInstance
);
1440 sInstance
->mShutdownComplete
= true;
1443 nsresult
QuotaManager::Observer::Init() {
1444 MOZ_ASSERT(NS_IsMainThread());
1446 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
1447 if (NS_WARN_IF(!obs
)) {
1448 return NS_ERROR_FAILURE
;
1451 // XXX: Improve the way that we remove observer in failure cases.
1452 nsresult rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
1453 if (NS_WARN_IF(NS_FAILED(rv
))) {
1457 rv
= obs
->AddObserver(this, kProfileDoChangeTopic
, false);
1458 if (NS_WARN_IF(NS_FAILED(rv
))) {
1459 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1463 rv
= obs
->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
, false);
1464 if (NS_WARN_IF(NS_FAILED(rv
))) {
1465 obs
->RemoveObserver(this, kProfileDoChangeTopic
);
1466 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1470 rv
= obs
->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC
, false);
1471 if (NS_WARN_IF(NS_FAILED(rv
))) {
1472 obs
->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
);
1473 obs
->RemoveObserver(this, kProfileDoChangeTopic
);
1474 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1478 rv
= obs
->AddObserver(this, kPrivateBrowsingObserverTopic
, false);
1479 if (NS_WARN_IF(NS_FAILED(rv
))) {
1480 obs
->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC
);
1481 obs
->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
);
1482 obs
->RemoveObserver(this, kProfileDoChangeTopic
);
1483 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1490 nsresult
QuotaManager::Observer::Shutdown() {
1491 MOZ_ASSERT(NS_IsMainThread());
1493 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
1494 if (NS_WARN_IF(!obs
)) {
1495 return NS_ERROR_FAILURE
;
1498 MOZ_ALWAYS_SUCCEEDS(obs
->RemoveObserver(this, kPrivateBrowsingObserverTopic
));
1499 MOZ_ALWAYS_SUCCEEDS(obs
->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC
));
1500 MOZ_ALWAYS_SUCCEEDS(
1501 obs
->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
));
1502 MOZ_ALWAYS_SUCCEEDS(obs
->RemoveObserver(this, kProfileDoChangeTopic
));
1503 MOZ_ALWAYS_SUCCEEDS(obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
));
1505 sInstance
= nullptr;
1507 // In general, the instance will have died after the latter removal call, so
1508 // it's not safe to do anything after that point.
1509 // However, Shutdown is currently called from Observe which is called by the
1510 // Observer Service which holds a strong reference to the observer while the
1511 // Observe method is being called.
1516 NS_IMPL_ISUPPORTS(QuotaManager::Observer
, nsIObserver
)
1519 QuotaManager::Observer::Observe(nsISupports
* aSubject
, const char* aTopic
,
1520 const char16_t
* aData
) {
1521 MOZ_ASSERT(NS_IsMainThread());
1525 if (!strcmp(aTopic
, kProfileDoChangeTopic
)) {
1526 if (NS_WARN_IF(gBasePath
)) {
1528 "profile-before-change-qm must precede repeated "
1529 "profile-do-change!");
1533 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns
, true);
1535 gBasePath
= new nsString();
1537 nsCOMPtr
<nsIFile
> baseDir
;
1538 rv
= NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR
,
1539 getter_AddRefs(baseDir
));
1540 if (NS_FAILED(rv
)) {
1541 rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
1542 getter_AddRefs(baseDir
));
1544 if (NS_WARN_IF(NS_FAILED(rv
))) {
1548 rv
= baseDir
->GetPath(*gBasePath
);
1549 if (NS_WARN_IF(NS_FAILED(rv
))) {
1553 gStorageName
= new nsString();
1555 rv
= Preferences::GetString("dom.quotaManager.storageName", *gStorageName
);
1556 if (NS_FAILED(rv
)) {
1557 *gStorageName
= kStorageName
;
1560 gBuildId
= new nsCString();
1562 nsCOMPtr
<nsIPlatformInfo
> platformInfo
=
1563 do_GetService("@mozilla.org/xre/app-info;1");
1564 if (NS_WARN_IF(!platformInfo
)) {
1565 return NS_ERROR_FAILURE
;
1568 rv
= platformInfo
->GetPlatformBuildID(*gBuildId
);
1569 if (NS_WARN_IF(NS_FAILED(rv
))) {
1576 if (!strcmp(aTopic
, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID
)) {
1577 if (NS_WARN_IF(!gBasePath
)) {
1578 NS_WARNING("profile-do-change must precede profile-before-change-qm!");
1582 // mPendingProfileChange is our re-entrancy guard (the nested event loop
1583 // below may cause re-entrancy).
1584 if (mPendingProfileChange
) {
1588 AutoRestore
<bool> pending(mPendingProfileChange
);
1589 mPendingProfileChange
= true;
1591 mShutdownComplete
= false;
1593 PBackgroundChild
* backgroundActor
=
1594 BackgroundChild::GetOrCreateForCurrentThread();
1595 if (NS_WARN_IF(!backgroundActor
)) {
1596 return NS_ERROR_FAILURE
;
1599 if (NS_WARN_IF(!backgroundActor
->SendShutdownQuotaManager())) {
1600 return NS_ERROR_FAILURE
;
1603 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
1604 "QuotaManager::Observer::Observe profile-before-change-qm"_ns
,
1605 [&]() { return mShutdownComplete
; }));
1607 gBasePath
= nullptr;
1609 gStorageName
= nullptr;
1613 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns
, false);
1618 if (!strcmp(aTopic
, kPrivateBrowsingObserverTopic
)) {
1619 auto* const quotaManagerService
= QuotaManagerService::GetOrCreate();
1620 if (NS_WARN_IF(!quotaManagerService
)) {
1621 return NS_ERROR_FAILURE
;
1624 nsCOMPtr
<nsIQuotaRequest
> request
;
1625 rv
= quotaManagerService
->ClearStoragesForPrivateBrowsing(
1626 nsGetterAddRefs(request
));
1627 if (NS_WARN_IF(NS_FAILED(rv
))) {
1634 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
1636 if (NS_WARN_IF(NS_FAILED(rv
))) {
1643 if (!strcmp(aTopic
, NS_WIDGET_WAKE_OBSERVER_TOPIC
)) {
1644 gLastOSWake
= TimeStamp::Now();
1649 NS_WARNING("Unknown observer topic!");
1653 /*******************************************************************************
1655 ******************************************************************************/
1657 QuotaManager::QuotaManager(const nsAString
& aBasePath
,
1658 const nsAString
& aStorageName
)
1659 : mQuotaMutex("QuotaManager.mQuotaMutex"),
1660 mBasePath(aBasePath
),
1661 mStorageName(aStorageName
),
1662 mTemporaryStorageUsage(0),
1663 mNextDirectoryLockId(0),
1664 mShutdownStorageOpCount(0),
1665 mStorageInitialized(false),
1666 mTemporaryStorageInitialized(false),
1667 mTemporaryStorageInitializedInternal(false),
1668 mCacheUsable(false) {
1669 AssertIsOnOwningThread();
1670 MOZ_ASSERT(!gInstance
);
1673 QuotaManager::~QuotaManager() {
1674 AssertIsOnOwningThread();
1675 MOZ_ASSERT(!gInstance
|| gInstance
== this);
1679 nsresult
QuotaManager::Initialize() {
1680 MOZ_ASSERT(NS_IsMainThread());
1682 nsresult rv
= Observer::Initialize();
1683 if (NS_WARN_IF(NS_FAILED(rv
))) {
1691 Result
<MovingNotNull
<RefPtr
<QuotaManager
>>, nsresult
>
1692 QuotaManager::GetOrCreate() {
1693 AssertIsOnBackgroundThread();
1696 return WrapMovingNotNullUnchecked(RefPtr
<QuotaManager
>{gInstance
});
1699 QM_TRY(OkIf(gBasePath
), Err(NS_ERROR_FAILURE
), [](const auto&) {
1701 "Trying to create QuotaManager before profile-do-change! "
1702 "Forgot to call do_get_profile()?");
1705 QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE
), [](const auto&) {
1707 "Trying to create QuotaManager after profile-before-change-qm!");
1710 auto instance
= MakeRefPtr
<QuotaManager
>(*gBasePath
, *gStorageName
);
1712 QM_TRY(MOZ_TO_RESULT(instance
->Init()));
1714 gInstance
= instance
;
1716 // Do this before clients have a chance to acquire a directory lock for the
1717 // private repository.
1718 gInstance
->ClearPrivateRepository();
1720 return WrapMovingNotNullUnchecked(std::move(instance
));
1723 Result
<Ok
, nsresult
> QuotaManager::EnsureCreated() {
1724 AssertIsOnBackgroundThread();
1726 QM_TRY_RETURN(GetOrCreate().map([](const auto& res
) { return Ok
{}; }))
1730 QuotaManager
* QuotaManager::Get() {
1731 // Does not return an owning reference.
1736 nsIObserver
* QuotaManager::GetObserver() {
1737 MOZ_ASSERT(NS_IsMainThread());
1739 return Observer::GetInstance();
1743 bool QuotaManager::IsShuttingDown() { return gShutdown
; }
1746 void QuotaManager::ShutdownInstance() {
1747 AssertIsOnBackgroundThread();
1750 auto recordTimeDeltaHelper
=
1751 MakeRefPtr
<RecordTimeDeltaHelper
>(Telemetry::QM_SHUTDOWN_TIME_V0
);
1753 recordTimeDeltaHelper
->Start();
1755 gInstance
->Shutdown();
1757 recordTimeDeltaHelper
->End();
1759 gInstance
= nullptr;
1761 // If we were never initialized, just set the flag to avoid late creation.
1765 RefPtr
<Runnable
> runnable
=
1766 NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
1767 []() { Observer::ShutdownCompleted(); });
1768 MOZ_ASSERT(runnable
);
1770 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable
.forget()));
1774 void QuotaManager::Reset() {
1775 AssertIsOnBackgroundThread();
1776 MOZ_ASSERT(!gInstance
);
1777 MOZ_ASSERT(gShutdown
);
1783 bool QuotaManager::IsOSMetadata(const nsAString
& aFileName
) {
1784 return mozilla::dom::quota::IsOSMetadata(aFileName
);
1788 bool QuotaManager::IsDotFile(const nsAString
& aFileName
) {
1789 return mozilla::dom::quota::IsDotFile(aFileName
);
1792 void QuotaManager::RegisterNormalOriginOp(
1793 NormalOriginOperationBase
& aNormalOriginOp
) {
1794 AssertIsOnBackgroundThread();
1796 if (!gNormalOriginOps
) {
1797 gNormalOriginOps
= new NormalOriginOpArray();
1800 gNormalOriginOps
->AppendElement(&aNormalOriginOp
);
1803 void QuotaManager::UnregisterNormalOriginOp(
1804 NormalOriginOperationBase
& aNormalOriginOp
) {
1805 AssertIsOnBackgroundThread();
1806 MOZ_ASSERT(gNormalOriginOps
);
1808 gNormalOriginOps
->RemoveElement(&aNormalOriginOp
);
1810 if (gNormalOriginOps
->IsEmpty()) {
1811 gNormalOriginOps
= nullptr;
1815 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl
& aLock
) {
1816 AssertIsOnOwningThread();
1818 mDirectoryLocks
.AppendElement(WrapNotNullUnchecked(&aLock
));
1820 if (aLock
.ShouldUpdateLockIdTable()) {
1821 MutexAutoLock
lock(mQuotaMutex
);
1823 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable
.Contains(aLock
.Id()));
1824 mDirectoryLockIdTable
.InsertOrUpdate(aLock
.Id(),
1825 WrapNotNullUnchecked(&aLock
));
1828 if (aLock
.ShouldUpdateLockTable()) {
1829 DirectoryLockTable
& directoryLockTable
=
1830 GetDirectoryLockTable(aLock
.GetPersistenceType());
1832 // XXX It seems that the contents of the array are never actually used, we
1833 // just use that like an inefficient use counter. Can't we just change
1834 // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
1836 .LookupOrInsertWith(
1839 if (!IsShuttingDown()) {
1840 UpdateOriginAccessTime(aLock
.GetPersistenceType(),
1841 aLock
.OriginMetadata());
1843 return MakeUnique
<nsTArray
<NotNull
<DirectoryLockImpl
*>>>();
1845 ->AppendElement(WrapNotNullUnchecked(&aLock
));
1848 aLock
.SetRegistered(true);
1851 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl
& aLock
) {
1852 AssertIsOnOwningThread();
1854 MOZ_ALWAYS_TRUE(mDirectoryLocks
.RemoveElement(&aLock
));
1856 if (aLock
.ShouldUpdateLockIdTable()) {
1857 MutexAutoLock
lock(mQuotaMutex
);
1859 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable
.Contains(aLock
.Id()));
1860 mDirectoryLockIdTable
.Remove(aLock
.Id());
1863 if (aLock
.ShouldUpdateLockTable()) {
1864 DirectoryLockTable
& directoryLockTable
=
1865 GetDirectoryLockTable(aLock
.GetPersistenceType());
1867 // ClearDirectoryLockTables may have been called, so the element or entire
1868 // array may not exist anymre.
1869 nsTArray
<NotNull
<DirectoryLockImpl
*>>* array
;
1870 if (directoryLockTable
.Get(aLock
.Origin(), &array
) &&
1871 array
->RemoveElement(&aLock
) && array
->IsEmpty()) {
1872 directoryLockTable
.Remove(aLock
.Origin());
1874 if (!IsShuttingDown()) {
1875 UpdateOriginAccessTime(aLock
.GetPersistenceType(),
1876 aLock
.OriginMetadata());
1881 aLock
.SetRegistered(false);
1884 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl
& aLock
) {
1885 AssertIsOnOwningThread();
1887 mPendingDirectoryLocks
.AppendElement(&aLock
);
1890 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl
& aLock
) {
1891 AssertIsOnOwningThread();
1893 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks
.RemoveElement(&aLock
));
1896 uint64_t QuotaManager::CollectOriginsForEviction(
1897 uint64_t aMinSizeToBeFreed
, nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
) {
1898 AssertIsOnOwningThread();
1899 MOZ_ASSERT(aLocks
.IsEmpty());
1901 // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
1902 // or maybe a generalization if that.
1904 struct MOZ_STACK_CLASS Helper final
{
1905 static void GetInactiveOriginInfos(
1906 const nsTArray
<NotNull
<RefPtr
<OriginInfo
>>>& aOriginInfos
,
1907 const nsTArray
<NotNull
<const DirectoryLockImpl
*>>& aLocks
,
1908 OriginInfosFlatTraversable
& aInactiveOriginInfos
) {
1909 for (const auto& originInfo
: aOriginInfos
) {
1910 MOZ_ASSERT(originInfo
->mGroupInfo
->mPersistenceType
!=
1911 PERSISTENCE_TYPE_PERSISTENT
);
1913 if (originInfo
->LockedPersisted()) {
1917 // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a
1918 // moz-extension origin, unlike websites (which may more likely using
1919 // the local data as a cache but still able to retrieve the same data
1920 // from the server side) extensions do not have the same data stored
1921 // anywhere else and evicting the data would result into potential data
1922 // loss for the users.
1924 // Also, unlike a website the extensions are explicitly installed and
1925 // uninstalled by the user and all data associated to the extension
1926 // principal will be completely removed once the addon is uninstalled.
1927 if (originInfo
->mGroupInfo
->mPersistenceType
!=
1928 PERSISTENCE_TYPE_TEMPORARY
&&
1929 originInfo
->IsExtensionOrigin()) {
1933 const auto originScope
= OriginScope::FromOrigin(originInfo
->mOrigin
);
1936 std::any_of(aLocks
.begin(), aLocks
.end(),
1937 [&originScope
](const DirectoryLockImpl
* const lock
) {
1938 return originScope
.Matches(lock
->GetOriginScope());
1942 MOZ_ASSERT(!originInfo
->mCanonicalQuotaObjects
.Count(),
1943 "Inactive origin shouldn't have open files!");
1944 aInactiveOriginInfos
.InsertElementSorted(
1945 originInfo
, OriginInfoAccessTimeComparator());
1951 // Split locks into separate arrays and filter out locks for persistent
1952 // storage, they can't block us.
1953 auto [temporaryStorageLocks
, defaultStorageLocks
,
1954 privateStorageLocks
] = [this] {
1955 nsTArray
<NotNull
<const DirectoryLockImpl
*>> temporaryStorageLocks
;
1956 nsTArray
<NotNull
<const DirectoryLockImpl
*>> defaultStorageLocks
;
1957 nsTArray
<NotNull
<const DirectoryLockImpl
*>> privateStorageLocks
;
1959 for (NotNull
<const DirectoryLockImpl
*> const lock
: mDirectoryLocks
) {
1960 const Nullable
<PersistenceType
>& persistenceType
=
1961 lock
->NullablePersistenceType();
1963 if (persistenceType
.IsNull()) {
1964 temporaryStorageLocks
.AppendElement(lock
);
1965 defaultStorageLocks
.AppendElement(lock
);
1966 } else if (persistenceType
.Value() == PERSISTENCE_TYPE_TEMPORARY
) {
1967 temporaryStorageLocks
.AppendElement(lock
);
1968 } else if (persistenceType
.Value() == PERSISTENCE_TYPE_DEFAULT
) {
1969 defaultStorageLocks
.AppendElement(lock
);
1970 } else if (persistenceType
.Value() == PERSISTENCE_TYPE_PRIVATE
) {
1971 privateStorageLocks
.AppendElement(lock
);
1973 MOZ_ASSERT(persistenceType
.Value() == PERSISTENCE_TYPE_PERSISTENT
);
1975 // Do nothing here, persistent origins don't need to be collected ever.
1979 return std::make_tuple(std::move(temporaryStorageLocks
),
1980 std::move(defaultStorageLocks
),
1981 std::move(privateStorageLocks
));
1984 // Enumerate and process inactive origins. This must be protected by the
1986 MutexAutoLock
lock(mQuotaMutex
);
1988 const auto [inactiveOrigins
, sizeToBeFreed
] =
1989 [this, &temporaryStorageLocks
= temporaryStorageLocks
,
1990 &defaultStorageLocks
= defaultStorageLocks
,
1991 &privateStorageLocks
= privateStorageLocks
, aMinSizeToBeFreed
] {
1992 nsTArray
<NotNull
<RefPtr
<const OriginInfo
>>> inactiveOrigins
;
1993 for (const auto& entry
: mGroupInfoPairs
) {
1994 const auto& pair
= entry
.GetData();
1996 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
1999 RefPtr
<GroupInfo
> groupInfo
=
2000 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
);
2002 Helper::GetInactiveOriginInfos(groupInfo
->mOriginInfos
,
2003 temporaryStorageLocks
,
2007 groupInfo
= pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
);
2009 Helper::GetInactiveOriginInfos(
2010 groupInfo
->mOriginInfos
, defaultStorageLocks
, inactiveOrigins
);
2013 groupInfo
= pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE
);
2015 Helper::GetInactiveOriginInfos(
2016 groupInfo
->mOriginInfos
, privateStorageLocks
, inactiveOrigins
);
2021 // Make sure the array is sorted correctly.
2022 const bool inactiveOriginsSorted
=
2023 std::is_sorted(inactiveOrigins
.cbegin(), inactiveOrigins
.cend(),
2024 [](const auto& lhs
, const auto& rhs
) {
2025 return lhs
->mAccessTime
< rhs
->mAccessTime
;
2027 MOZ_ASSERT(inactiveOriginsSorted
);
2030 // Create a list of inactive and the least recently used origins
2031 // whose aggregate size is greater or equals the minimal size to be
2033 uint64_t sizeToBeFreed
= 0;
2034 for (uint32_t count
= inactiveOrigins
.Length(), index
= 0;
2035 index
< count
; index
++) {
2036 if (sizeToBeFreed
>= aMinSizeToBeFreed
) {
2037 inactiveOrigins
.TruncateLength(index
);
2041 sizeToBeFreed
+= inactiveOrigins
[index
]->LockedUsage();
2044 return std::pair(std::move(inactiveOrigins
), sizeToBeFreed
);
2047 if (sizeToBeFreed
>= aMinSizeToBeFreed
) {
2048 // Success, add directory locks for these origins, so any other
2049 // operations for them will be delayed (until origin eviction is finalized).
2051 for (const auto& originInfo
: inactiveOrigins
) {
2052 auto lock
= DirectoryLockImpl::CreateForEviction(
2053 WrapNotNullUnchecked(this), originInfo
->mGroupInfo
->mPersistenceType
,
2054 originInfo
->FlattenToOriginMetadata());
2056 lock
->AcquireImmediately();
2058 aLocks
.AppendElement(lock
.forget());
2061 return sizeToBeFreed
;
2067 nsresult
QuotaManager::Init() {
2068 AssertIsOnOwningThread();
2071 CacheUseDOSDevicePathSyntaxPrefValue();
2074 QM_TRY_INSPECT(const auto& baseDir
, QM_NewLocalFile(mBasePath
));
2077 do_Init(mIndexedDBPath
),
2078 GetPathForStorage(*baseDir
, nsLiteralString(INDEXEDDB_DIRECTORY_NAME
)));
2080 QM_TRY(MOZ_TO_RESULT(baseDir
->Append(mStorageName
)));
2082 QM_TRY_UNWRAP(do_Init(mStoragePath
),
2083 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString
, baseDir
, GetPath
));
2086 do_Init(mStorageArchivesPath
),
2087 GetPathForStorage(*baseDir
, nsLiteralString(ARCHIVES_DIRECTORY_NAME
)));
2090 do_Init(mPermanentStoragePath
),
2091 GetPathForStorage(*baseDir
, nsLiteralString(PERMANENT_DIRECTORY_NAME
)));
2094 do_Init(mTemporaryStoragePath
),
2095 GetPathForStorage(*baseDir
, nsLiteralString(TEMPORARY_DIRECTORY_NAME
)));
2098 do_Init(mDefaultStoragePath
),
2099 GetPathForStorage(*baseDir
, nsLiteralString(DEFAULT_DIRECTORY_NAME
)));
2102 do_Init(mPrivateStoragePath
),
2103 GetPathForStorage(*baseDir
, nsLiteralString(PRIVATE_DIRECTORY_NAME
)));
2106 do_Init(mToBeRemovedStoragePath
),
2107 GetPathForStorage(*baseDir
, nsLiteralString(TOBEREMOVED_DIRECTORY_NAME
)));
2109 QM_TRY_UNWRAP(do_Init(mIOThread
),
2110 MOZ_TO_RESULT_INVOKE_TYPED(
2111 nsCOMPtr
<nsIThread
>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread
),
2112 "QuotaManager IO"));
2114 static_assert(Client::IDB
== 0 && Client::DOMCACHE
== 1 && Client::SDB
== 2 &&
2115 Client::FILESYSTEM
== 3 && Client::LS
== 4 &&
2116 Client::TYPE_MAX
== 5,
2117 "Fix the registration!");
2119 // Register clients.
2120 auto clients
= decltype(mClients
)::ValueType
{};
2121 clients
.AppendElement(indexedDB::CreateQuotaClient());
2122 clients
.AppendElement(cache::CreateQuotaClient());
2123 clients
.AppendElement(simpledb::CreateQuotaClient());
2124 clients
.AppendElement(fs::CreateQuotaClient());
2125 if (NextGenLocalStorageEnabled()) {
2126 clients
.AppendElement(localstorage::CreateQuotaClient());
2128 clients
.SetLength(Client::TypeMax());
2131 mClients
.init(std::move(clients
));
2133 MOZ_ASSERT(mClients
->Capacity() == Client::TYPE_MAX
,
2134 "Should be using an auto array with correct capacity!");
2136 mAllClientTypes
.init(ClientTypesArray
{
2137 Client::Type::IDB
, Client::Type::DOMCACHE
, Client::Type::SDB
,
2138 Client::Type::FILESYSTEM
, Client::Type::LS
});
2139 mAllClientTypesExceptLS
.init(
2140 ClientTypesArray
{Client::Type::IDB
, Client::Type::DOMCACHE
,
2141 Client::Type::SDB
, Client::Type::FILESYSTEM
});
2147 void QuotaManager::MaybeRecordQuotaClientShutdownStep(
2148 const Client::Type aClientType
, const nsACString
& aStepDescription
) {
2149 // Callable on any thread.
2151 auto* const quotaManager
= QuotaManager::Get();
2152 MOZ_DIAGNOSTIC_ASSERT(quotaManager
);
2154 if (quotaManager
->IsShuttingDown()) {
2155 quotaManager
->RecordShutdownStep(Some(aClientType
), aStepDescription
);
2160 void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
2161 const Client::Type aClientType
, const nsACString
& aStepDescription
) {
2162 // Callable on any thread.
2164 auto* const quotaManager
= QuotaManager::Get();
2166 if (quotaManager
&& quotaManager
->IsShuttingDown()) {
2167 quotaManager
->RecordShutdownStep(Some(aClientType
), aStepDescription
);
2171 void QuotaManager::RecordQuotaManagerShutdownStep(
2172 const nsACString
& aStepDescription
) {
2173 // Callable on any thread.
2174 MOZ_ASSERT(IsShuttingDown());
2176 RecordShutdownStep(Nothing
{}, aStepDescription
);
2179 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
2180 const nsACString
& aStepDescription
) {
2181 // Callable on any thread.
2183 if (IsShuttingDown()) {
2184 RecordQuotaManagerShutdownStep(aStepDescription
);
2188 void QuotaManager::RecordShutdownStep(const Maybe
<Client::Type
> aClientType
,
2189 const nsACString
& aStepDescription
) {
2190 MOZ_ASSERT(IsShuttingDown());
2192 const TimeDuration elapsedSinceShutdownStart
=
2193 TimeStamp::NowLoRes() - *mShutdownStartedAt
;
2195 const auto stepString
=
2196 nsPrintfCString("%fs: %s", elapsedSinceShutdownStart
.ToSeconds(),
2197 nsPromiseFlatCString(aStepDescription
).get());
2200 AssertIsOnBackgroundThread();
2202 mShutdownSteps
[*aClientType
].Append(stepString
+ "\n"_ns
);
2204 // Callable on any thread.
2205 MutexAutoLock
lock(mQuotaMutex
);
2207 mQuotaManagerShutdownSteps
.Append(stepString
+ "\n"_ns
);
2211 // XXX Probably this isn't the mechanism that should be used here.
2215 nsAutoCString(aClientType
? Client::TypeToText(*aClientType
)
2216 : "quota manager"_ns
+ " shutdown step"_ns
)
2218 stepString
.get(), __FILE__
, __LINE__
);
2222 void QuotaManager::Shutdown() {
2223 AssertIsOnOwningThread();
2224 MOZ_DIAGNOSTIC_ASSERT(!gShutdown
);
2226 // Define some local helper functions
2228 auto flagShutdownStarted
= [this]() {
2229 mShutdownStartedAt
.init(TimeStamp::NowLoRes());
2231 // Setting this flag prevents the service from being recreated and prevents
2232 // further storages from being created.
2236 nsCOMPtr
<nsITimer
> crashBrowserTimer
;
2238 auto crashBrowserTimerCallback
= [](nsITimer
* aTimer
, void* aClosure
) {
2239 auto* const quotaManager
= static_cast<QuotaManager
*>(aClosure
);
2241 nsCString annotation
;
2243 for (Client::Type type
: quotaManager
->AllClientTypes()) {
2244 auto& quotaClient
= *(*quotaManager
->mClients
)[type
];
2246 if (!quotaClient
.IsShutdownCompleted()) {
2247 annotation
.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
2248 Client::TypeToText(type
).get(),
2249 quotaClient
.GetShutdownStatus().get(),
2250 quotaManager
->mShutdownSteps
[type
].get());
2254 if (gNormalOriginOps
) {
2255 annotation
.AppendPrintf("QM: %zu normal origin ops pending\n",
2256 gNormalOriginOps
->Length());
2257 #ifdef QM_COLLECTING_OPERATION_TELEMETRY
2258 for (const auto& op
: *gNormalOriginOps
) {
2259 annotation
.AppendPrintf("Op: %s pending\n", op
->Name());
2264 MutexAutoLock
lock(quotaManager
->mQuotaMutex
);
2266 annotation
.AppendPrintf("Intermediate steps:\n%s\n",
2267 quotaManager
->mQuotaManagerShutdownSteps
.get());
2270 CrashReporter::AnnotateCrashReport(
2271 CrashReporter::Annotation::QuotaManagerShutdownTimeout
, annotation
);
2273 MOZ_CRASH("Quota manager shutdown timed out");
2276 auto startCrashBrowserTimer
= [&]() {
2277 crashBrowserTimer
= NS_NewTimer();
2278 MOZ_ASSERT(crashBrowserTimer
);
2279 if (crashBrowserTimer
) {
2280 RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns
);
2281 MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer
->InitWithNamedFuncCallback(
2282 crashBrowserTimerCallback
, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS
,
2283 nsITimer::TYPE_ONE_SHOT
,
2284 "quota::QuotaManager::Shutdown::crashBrowserTimer"));
2288 auto stopCrashBrowserTimer
= [&]() {
2289 if (crashBrowserTimer
) {
2290 RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns
);
2291 QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer
->Cancel()));
2295 auto initiateShutdownWorkThreads
= [this]() {
2296 RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns
);
2297 bool needsToWait
= false;
2298 for (Client::Type type
: AllClientTypes()) {
2299 // Clients are supposed to also AbortAllOperations from this point on
2300 // to speed up shutdown, if possible. Thus pending operations
2301 // might not be executed anymore.
2302 needsToWait
|= (*mClients
)[type
]->InitiateShutdownWorkThreads();
2308 nsCOMPtr
<nsITimer
> killActorsTimer
;
2310 auto killActorsTimerCallback
= [](nsITimer
* aTimer
, void* aClosure
) {
2311 auto* const quotaManager
= static_cast<QuotaManager
*>(aClosure
);
2313 quotaManager
->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns
);
2315 // XXX: This abort is a workaround to unblock shutdown, which
2316 // ought to be removed by bug 1682326. We probably need more
2317 // checks to immediately abort new operations during
2319 quotaManager
->GetClient(Client::IDB
)->AbortAllOperations();
2321 for (Client::Type type
: quotaManager
->AllClientTypes()) {
2322 quotaManager
->GetClient(type
)->ForceKillActors();
2326 auto startKillActorsTimer
= [&]() {
2327 killActorsTimer
= NS_NewTimer();
2328 MOZ_ASSERT(killActorsTimer
);
2329 if (killActorsTimer
) {
2330 RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns
);
2331 MOZ_ALWAYS_SUCCEEDS(killActorsTimer
->InitWithNamedFuncCallback(
2332 killActorsTimerCallback
, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS
,
2333 nsITimer::TYPE_ONE_SHOT
,
2334 "quota::QuotaManager::Shutdown::killActorsTimer"));
2338 auto stopKillActorsTimer
= [&]() {
2339 if (killActorsTimer
) {
2340 RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns
);
2341 QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer
->Cancel()));
2345 auto isAllClientsShutdownComplete
= [this] {
2346 return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
2347 [&self
= *this](const auto type
) {
2348 return (*self
.mClients
)[type
]->IsShutdownCompleted();
2352 auto shutdownAndJoinWorkThreads
= [this]() {
2353 RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns
);
2354 for (Client::Type type
: AllClientTypes()) {
2355 (*mClients
)[type
]->FinalizeShutdownWorkThreads();
2359 auto shutdownAndJoinIOThread
= [this]() {
2360 RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns
);
2362 // Make sure to join with our IO thread.
2363 QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread
)->Shutdown()));
2366 auto invalidatePendingDirectoryLocks
= [this]() {
2367 RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns
);
2368 for (RefPtr
<DirectoryLockImpl
>& lock
: mPendingDirectoryLocks
) {
2373 // Body of the function
2375 ScopedLogExtraInfo scope
{ScopedLogExtraInfo::kTagContext
,
2376 "dom::quota::QuotaManager::Shutdown"_ns
};
2378 // This must be called before `flagShutdownStarted`, it would fail otherwise.
2379 // `ShutdownStorageOp` needs to acquire an exclusive directory lock over
2380 // entire <profile>/storage which will abort any existing operations and wait
2381 // for all existing directory locks to be released. So the shutdown operation
2382 // will effectively run after all existing operations.
2383 // We don't need to use the returned promise here because `ShutdownStorage`
2384 // registers `ShudownStorageOp` in `gNormalOriginOps`.
2387 flagShutdownStarted();
2389 startCrashBrowserTimer();
2391 // XXX: StopIdleMaintenance now just notifies all clients to abort any
2392 // maintenance work.
2393 // This could be done as part of QuotaClient::AbortAllOperations.
2394 StopIdleMaintenance();
2396 // XXX In theory, we could simplify the code below (and also the `Client`
2397 // interface) by removing the `initiateShutdownWorkThreads` and
2398 // `isAllClientsShutdownComplete` calls because it should be sufficient
2399 // to rely on `ShutdownStorage` to abort all existing operations and to
2400 // wait for all existing directory locks to be released as well.
2402 const bool needsToWait
=
2403 initiateShutdownWorkThreads() || static_cast<bool>(gNormalOriginOps
);
2405 // If any clients cannot shutdown immediately, spin the event loop while we
2406 // wait on all the threads to close.
2408 startKillActorsTimer();
2410 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
2411 "QuotaManager::Shutdown"_ns
, [isAllClientsShutdownComplete
]() {
2412 return !gNormalOriginOps
&& isAllClientsShutdownComplete();
2415 stopKillActorsTimer();
2418 shutdownAndJoinWorkThreads();
2420 shutdownAndJoinIOThread();
2422 invalidatePendingDirectoryLocks();
2424 stopCrashBrowserTimer();
2427 void QuotaManager::InitQuotaForOrigin(
2428 const FullOriginMetadata
& aFullOriginMetadata
,
2429 const ClientUsageArray
& aClientUsages
, uint64_t aUsageBytes
) {
2430 AssertIsOnIOThread();
2431 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata
.mPersistenceType
));
2433 MutexAutoLock
lock(mQuotaMutex
);
2435 RefPtr
<GroupInfo
> groupInfo
= LockedGetOrCreateGroupInfo(
2436 aFullOriginMetadata
.mPersistenceType
, aFullOriginMetadata
.mSuffix
,
2437 aFullOriginMetadata
.mGroup
);
2439 groupInfo
->LockedAddOriginInfo(MakeNotNull
<RefPtr
<OriginInfo
>>(
2440 groupInfo
, aFullOriginMetadata
.mOrigin
,
2441 aFullOriginMetadata
.mStorageOrigin
, aFullOriginMetadata
.mIsPrivate
,
2442 aClientUsages
, aUsageBytes
, aFullOriginMetadata
.mLastAccessTime
,
2443 aFullOriginMetadata
.mPersisted
,
2444 /* aDirectoryExists */ true));
2447 void QuotaManager::EnsureQuotaForOrigin(const OriginMetadata
& aOriginMetadata
) {
2448 AssertIsOnIOThread();
2449 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata
.mPersistenceType
));
2451 MutexAutoLock
lock(mQuotaMutex
);
2453 RefPtr
<GroupInfo
> groupInfo
= LockedGetOrCreateGroupInfo(
2454 aOriginMetadata
.mPersistenceType
, aOriginMetadata
.mSuffix
,
2455 aOriginMetadata
.mGroup
);
2457 RefPtr
<OriginInfo
> originInfo
=
2458 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
2460 groupInfo
->LockedAddOriginInfo(MakeNotNull
<RefPtr
<OriginInfo
>>(
2461 groupInfo
, aOriginMetadata
.mOrigin
, aOriginMetadata
.mStorageOrigin
,
2462 aOriginMetadata
.mIsPrivate
, ClientUsageArray(),
2463 /* aUsageBytes */ 0,
2464 /* aAccessTime */ PR_Now(), /* aPersisted */ false,
2465 /* aDirectoryExists */ false));
2469 int64_t QuotaManager::NoteOriginDirectoryCreated(
2470 const OriginMetadata
& aOriginMetadata
, bool aPersisted
) {
2471 AssertIsOnIOThread();
2472 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 timestamp
= originInfo
->LockedAccessTime();
2486 originInfo
->mPersisted
= aPersisted
;
2487 originInfo
->mDirectoryExists
= true;
2489 timestamp
= PR_Now();
2490 groupInfo
->LockedAddOriginInfo(MakeNotNull
<RefPtr
<OriginInfo
>>(
2491 groupInfo
, aOriginMetadata
.mOrigin
, aOriginMetadata
.mStorageOrigin
,
2492 aOriginMetadata
.mIsPrivate
, ClientUsageArray(),
2493 /* aUsageBytes */ 0,
2494 /* aAccessTime */ timestamp
, aPersisted
, /* aDirectoryExists */ true));
2500 void QuotaManager::DecreaseUsageForClient(const ClientMetadata
& aClientMetadata
,
2502 MOZ_ASSERT(!NS_IsMainThread());
2503 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata
.mPersistenceType
));
2505 MutexAutoLock
lock(mQuotaMutex
);
2507 GroupInfoPair
* pair
;
2508 if (!mGroupInfoPairs
.Get(aClientMetadata
.mGroup
, &pair
)) {
2512 RefPtr
<GroupInfo
> groupInfo
=
2513 pair
->LockedGetGroupInfo(aClientMetadata
.mPersistenceType
);
2518 RefPtr
<OriginInfo
> originInfo
=
2519 groupInfo
->LockedGetOriginInfo(aClientMetadata
.mOrigin
);
2521 originInfo
->LockedDecreaseUsage(aClientMetadata
.mClientType
, aSize
);
2525 void QuotaManager::ResetUsageForClient(const ClientMetadata
& aClientMetadata
) {
2526 MOZ_ASSERT(!NS_IsMainThread());
2527 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata
.mPersistenceType
));
2529 MutexAutoLock
lock(mQuotaMutex
);
2531 GroupInfoPair
* pair
;
2532 if (!mGroupInfoPairs
.Get(aClientMetadata
.mGroup
, &pair
)) {
2536 RefPtr
<GroupInfo
> groupInfo
=
2537 pair
->LockedGetGroupInfo(aClientMetadata
.mPersistenceType
);
2542 RefPtr
<OriginInfo
> originInfo
=
2543 groupInfo
->LockedGetOriginInfo(aClientMetadata
.mOrigin
);
2545 originInfo
->LockedResetUsageForClient(aClientMetadata
.mClientType
);
2549 UsageInfo
QuotaManager::GetUsageForClient(PersistenceType aPersistenceType
,
2550 const OriginMetadata
& aOriginMetadata
,
2551 Client::Type aClientType
) {
2552 MOZ_ASSERT(!NS_IsMainThread());
2553 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
2555 MutexAutoLock
lock(mQuotaMutex
);
2557 GroupInfoPair
* pair
;
2558 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
2562 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
2567 RefPtr
<OriginInfo
> originInfo
=
2568 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
2573 return originInfo
->LockedGetUsageForClient(aClientType
);
2576 void QuotaManager::UpdateOriginAccessTime(
2577 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
) {
2578 AssertIsOnOwningThread();
2579 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
2580 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
== aPersistenceType
);
2581 MOZ_ASSERT(!IsShuttingDown());
2583 MutexAutoLock
lock(mQuotaMutex
);
2585 GroupInfoPair
* pair
;
2586 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
2590 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
2595 RefPtr
<OriginInfo
> originInfo
=
2596 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
2598 int64_t timestamp
= PR_Now();
2599 originInfo
->LockedUpdateAccessTime(timestamp
);
2601 MutexAutoUnlock
autoUnlock(mQuotaMutex
);
2603 auto op
= CreateSaveOriginAccessTimeOp(WrapMovingNotNullUnchecked(this),
2604 aOriginMetadata
, timestamp
);
2606 RegisterNormalOriginOp(*op
);
2608 op
->RunImmediately();
2612 void QuotaManager::RemoveQuota() {
2613 AssertIsOnIOThread();
2615 MutexAutoLock
lock(mQuotaMutex
);
2617 for (const auto& entry
: mGroupInfoPairs
) {
2618 const auto& pair
= entry
.GetData();
2620 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
2623 RefPtr
<GroupInfo
> groupInfo
=
2624 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
);
2626 groupInfo
->LockedRemoveOriginInfos();
2629 groupInfo
= pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
);
2631 groupInfo
->LockedRemoveOriginInfos();
2634 groupInfo
= pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE
);
2636 groupInfo
->LockedRemoveOriginInfos();
2640 mGroupInfoPairs
.Clear();
2642 MOZ_ASSERT(mTemporaryStorageUsage
== 0, "Should be zero!");
2645 nsresult
QuotaManager::LoadQuota() {
2646 AssertIsOnIOThread();
2647 MOZ_ASSERT(mStorageConnection
);
2648 MOZ_ASSERT(!mTemporaryStorageInitializedInternal
);
2650 // A list of all unaccessed default or temporary origins.
2651 nsTArray
<FullOriginMetadata
> unaccessedOrigins
;
2653 auto MaybeCollectUnaccessedOrigin
=
2654 [loadQuotaInfoStartTime
= PR_Now(),
2655 &unaccessedOrigins
](auto& fullOriginMetadata
) {
2656 if (IsOriginUnaccessed(fullOriginMetadata
, loadQuotaInfoStartTime
)) {
2657 unaccessedOrigins
.AppendElement(std::move(fullOriginMetadata
));
2661 auto recordTimeDeltaHelper
=
2662 MakeRefPtr
<RecordTimeDeltaHelper
>(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0
);
2664 const auto startTime
= recordTimeDeltaHelper
->Start();
2666 auto LoadQuotaFromCache
= [&]() -> nsresult
{
2669 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2670 nsCOMPtr
<mozIStorageStatement
>, mStorageConnection
, CreateStatement
,
2671 "SELECT repository_id, suffix, group_, "
2672 "origin, client_usages, usage, "
2673 "last_access_time, accessed, persisted "
2676 auto autoRemoveQuota
= MakeScopeExit([&] {
2678 unaccessedOrigins
.Clear();
2681 QM_TRY(quota::CollectWhileHasResult(
2684 &MaybeCollectUnaccessedOrigin
](auto& stmt
) -> Result
<Ok
, nsresult
> {
2685 QM_TRY_INSPECT(const int32_t& repositoryId
,
2686 MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt32
, 0));
2688 const auto maybePersistenceType
=
2689 PersistenceTypeFromInt32(repositoryId
, fallible
);
2690 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
2692 FullOriginMetadata fullOriginMetadata
;
2694 fullOriginMetadata
.mPersistenceType
= maybePersistenceType
.value();
2696 QM_TRY_UNWRAP(fullOriginMetadata
.mSuffix
,
2697 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString
, stmt
,
2700 QM_TRY_UNWRAP(fullOriginMetadata
.mGroup
,
2701 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString
, stmt
,
2704 QM_TRY_UNWRAP(fullOriginMetadata
.mOrigin
,
2705 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString
, stmt
,
2708 fullOriginMetadata
.mStorageOrigin
= fullOriginMetadata
.mOrigin
;
2710 fullOriginMetadata
.mIsPrivate
= false;
2712 QM_TRY_INSPECT(const auto& clientUsagesText
,
2713 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString
, stmt
,
2716 ClientUsageArray clientUsages
;
2717 QM_TRY(MOZ_TO_RESULT(clientUsages
.Deserialize(clientUsagesText
)));
2719 QM_TRY_INSPECT(const int64_t& usage
,
2720 MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt64
, 5));
2721 QM_TRY_UNWRAP(fullOriginMetadata
.mLastAccessTime
,
2722 MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt64
, 6));
2723 QM_TRY_INSPECT(const int64_t& accessed
,
2724 MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt32
, 7));
2725 QM_TRY_UNWRAP(fullOriginMetadata
.mPersisted
,
2726 MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt32
, 8));
2728 QM_TRY_INSPECT(const bool& groupUpdated
,
2729 MaybeUpdateGroupForOrigin(fullOriginMetadata
));
2731 Unused
<< groupUpdated
;
2734 const bool& lastAccessTimeUpdated
,
2735 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata
));
2737 Unused
<< lastAccessTimeUpdated
;
2739 // We don't need to update the .metadata-v2 file on disk here,
2740 // EnsureTemporaryOriginIsInitialized is responsible for doing that.
2741 // We just need to use correct group and last access time before
2742 // initializing quota for the given origin. (Note that calling
2743 // LoadFullOriginMetadataWithRestore below might update the group in
2744 // the metadata file, but only as a side-effect. The actual place we
2745 // ensure consistency is in EnsureTemporaryOriginIsInitialized.)
2748 QM_TRY_INSPECT(const auto& directory
,
2749 GetOriginDirectory(fullOriginMetadata
));
2751 QM_TRY_INSPECT(const bool& exists
,
2752 MOZ_TO_RESULT_INVOKE_MEMBER(directory
, Exists
));
2754 QM_TRY(OkIf(exists
), Err(NS_ERROR_FILE_NOT_FOUND
));
2756 QM_TRY_INSPECT(const bool& isDirectory
,
2757 MOZ_TO_RESULT_INVOKE_MEMBER(directory
, IsDirectory
));
2759 QM_TRY(OkIf(isDirectory
), Err(NS_ERROR_FILE_DESTINATION_NOT_DIR
));
2761 // Calling LoadFullOriginMetadataWithRestore might update the group
2762 // in the metadata file, but only as a side-effect. The actual place
2763 // we ensure consistency is in EnsureTemporaryOriginIsInitialized.
2765 QM_TRY_INSPECT(const auto& metadata
,
2766 LoadFullOriginMetadataWithRestore(directory
));
2768 QM_WARNONLY_TRY(OkIf(fullOriginMetadata
.mLastAccessTime
==
2769 metadata
.mLastAccessTime
));
2771 QM_TRY(OkIf(fullOriginMetadata
.mPersisted
== metadata
.mPersisted
),
2772 Err(NS_ERROR_FAILURE
));
2774 QM_TRY(OkIf(fullOriginMetadata
.mPersistenceType
==
2775 metadata
.mPersistenceType
),
2776 Err(NS_ERROR_FAILURE
));
2778 QM_TRY(OkIf(fullOriginMetadata
.mSuffix
== metadata
.mSuffix
),
2779 Err(NS_ERROR_FAILURE
));
2781 QM_TRY(OkIf(fullOriginMetadata
.mGroup
== metadata
.mGroup
),
2782 Err(NS_ERROR_FAILURE
));
2784 QM_TRY(OkIf(fullOriginMetadata
.mOrigin
== metadata
.mOrigin
),
2785 Err(NS_ERROR_FAILURE
));
2787 QM_TRY(OkIf(fullOriginMetadata
.mStorageOrigin
==
2788 metadata
.mStorageOrigin
),
2789 Err(NS_ERROR_FAILURE
));
2791 QM_TRY(OkIf(fullOriginMetadata
.mIsPrivate
== metadata
.mIsPrivate
),
2792 Err(NS_ERROR_FAILURE
));
2794 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
2795 fullOriginMetadata
.mPersistenceType
, fullOriginMetadata
,
2796 fullOriginMetadata
.mLastAccessTime
,
2797 fullOriginMetadata
.mPersisted
, directory
)));
2799 InitQuotaForOrigin(fullOriginMetadata
, clientUsages
, usage
);
2802 MaybeCollectUnaccessedOrigin(fullOriginMetadata
);
2807 autoRemoveQuota
.release();
2813 const bool& loadQuotaFromCache
, ([this]() -> Result
<bool, nsresult
> {
2817 CreateAndExecuteSingleStepStatement
<
2818 SingleStepResult::ReturnNullIfNoResult
>(
2819 *mStorageConnection
, "SELECT valid, build_id FROM cache"_ns
));
2821 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FILE_CORRUPTED
));
2823 QM_TRY_INSPECT(const int32_t& valid
,
2824 MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt32
, 0));
2827 if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) {
2831 QM_TRY_INSPECT(const auto& buildId
,
2832 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2833 nsAutoCString
, stmt
, GetUTF8String
, 1));
2835 return buildId
== *gBuildId
;
2842 auto autoRemoveQuota
= MakeScopeExit([&] { RemoveQuota(); });
2844 if (!loadQuotaFromCache
||
2845 !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
2846 ![&LoadQuotaFromCache
] {
2847 QM_WARNONLY_TRY_UNWRAP(auto res
, MOZ_TO_RESULT(LoadQuotaFromCache()));
2848 return static_cast<bool>(res
);
2850 // A keeper to defer the return only in Nightly, so that the telemetry data
2851 // for whole profile can be collected.
2852 #ifdef NIGHTLY_BUILD
2853 nsresult statusKeeper
= NS_OK
;
2856 const auto statusKeeperFunc
= [&](const nsresult rv
) {
2857 RECORD_IN_NIGHTLY(statusKeeper
, rv
);
2860 for (const PersistenceType type
:
2861 kInitializableBestEffortPersistenceTypes
) {
2862 if (NS_WARN_IF(IsShuttingDown())) {
2863 RETURN_STATUS_OR_RESULT(statusKeeper
, NS_ERROR_ABORT
);
2866 QM_TRY(([&]() -> Result
<Ok
, nsresult
> {
2867 QM_TRY(MOZ_TO_RESULT(([this, type
, &MaybeCollectUnaccessedOrigin
] {
2868 const auto innerFunc
= [&](const auto&) -> nsresult
{
2869 return InitializeRepository(type
,
2870 MaybeCollectUnaccessedOrigin
);
2873 return ExecuteInitialization(
2874 type
== PERSISTENCE_TYPE_DEFAULT
2875 ? Initialization::DefaultRepository
2876 : Initialization::TemporaryRepository
,
2879 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
2885 #ifdef NIGHTLY_BUILD
2886 if (NS_FAILED(statusKeeper
)) {
2887 return statusKeeper
;
2892 autoRemoveQuota
.release();
2894 const auto endTime
= recordTimeDeltaHelper
->End();
2896 if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() &&
2897 static_cast<uint32_t>((endTime
- startTime
).ToMilliseconds()) >=
2898 StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() &&
2899 !unaccessedOrigins
.IsEmpty()) {
2900 QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins
));
2906 void QuotaManager::UnloadQuota() {
2907 AssertIsOnIOThread();
2908 MOZ_ASSERT(mStorageConnection
);
2909 MOZ_ASSERT(mTemporaryStorageInitializedInternal
);
2910 MOZ_ASSERT(mCacheUsable
);
2912 auto autoRemoveQuota
= MakeScopeExit([&] { RemoveQuota(); });
2914 mozStorageTransaction
transaction(
2915 mStorageConnection
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
2917 QM_TRY(MOZ_TO_RESULT(transaction
.Start()), QM_VOID
);
2919 QM_TRY(MOZ_TO_RESULT(
2920 mStorageConnection
->ExecuteSimpleSQL("DELETE FROM origin;"_ns
)),
2923 nsCOMPtr
<mozIStorageStatement
> insertStmt
;
2926 MutexAutoLock
lock(mQuotaMutex
);
2928 for (auto iter
= mGroupInfoPairs
.Iter(); !iter
.Done(); iter
.Next()) {
2929 MOZ_ASSERT(!iter
.Key().IsEmpty());
2931 GroupInfoPair
* const pair
= iter
.UserData();
2934 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
2935 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(type
);
2940 for (const auto& originInfo
: groupInfo
->mOriginInfos
) {
2941 MOZ_ASSERT(!originInfo
->mCanonicalQuotaObjects
.Count());
2943 if (!originInfo
->mDirectoryExists
) {
2947 if (originInfo
->mIsPrivate
) {
2952 MOZ_ALWAYS_SUCCEEDS(insertStmt
->Reset());
2956 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2957 nsCOMPtr
<mozIStorageStatement
>, mStorageConnection
,
2959 "INSERT INTO origin (repository_id, suffix, group_, "
2960 "origin, client_usages, usage, last_access_time, "
2961 "accessed, persisted) "
2962 "VALUES (:repository_id, :suffix, :group_, :origin, "
2963 ":client_usages, :usage, :last_access_time, :accessed, "
2968 QM_TRY(MOZ_TO_RESULT(originInfo
->LockedBindToStatement(insertStmt
)),
2971 QM_TRY(MOZ_TO_RESULT(insertStmt
->Execute()), QM_VOID
);
2974 groupInfo
->LockedRemoveOriginInfos();
2983 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2984 nsCOMPtr
<mozIStorageStatement
>, mStorageConnection
, CreateStatement
,
2985 "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns
),
2988 QM_TRY(MOZ_TO_RESULT(stmt
->BindInt32ByName("valid"_ns
, 1)), QM_VOID
);
2989 QM_TRY(MOZ_TO_RESULT(stmt
->BindUTF8StringByName("buildId"_ns
, *gBuildId
)),
2991 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()), QM_VOID
);
2992 QM_TRY(MOZ_TO_RESULT(transaction
.Commit()), QM_VOID
);
2995 already_AddRefed
<QuotaObject
> QuotaManager::GetQuotaObject(
2996 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
2997 Client::Type aClientType
, nsIFile
* aFile
, int64_t aFileSize
,
2998 int64_t* aFileSizeOut
/* = nullptr */) {
2999 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3000 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
== aPersistenceType
);
3006 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
3010 QM_TRY_INSPECT(const auto& path
,
3011 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString
, aFile
, GetPath
),
3016 QM_TRY_INSPECT(const auto& directory
, GetOriginDirectory(aOriginMetadata
),
3019 nsAutoString clientType
;
3020 QM_TRY(OkIf(Client::TypeToText(aClientType
, clientType
, fallible
)),
3023 QM_TRY(MOZ_TO_RESULT(directory
->Append(clientType
)), nullptr);
3026 const auto& directoryPath
,
3027 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString
, directory
, GetPath
),
3030 MOZ_ASSERT(StringBeginsWith(path
, directoryPath
));
3035 const int64_t fileSize
,
3036 ([&aFile
, aFileSize
]() -> Result
<int64_t, nsresult
> {
3037 if (aFileSize
== -1) {
3038 QM_TRY_INSPECT(const bool& exists
,
3039 MOZ_TO_RESULT_INVOKE_MEMBER(aFile
, Exists
));
3042 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile
, GetFileSize
));
3052 RefPtr
<QuotaObject
> result
;
3054 MutexAutoLock
lock(mQuotaMutex
);
3056 GroupInfoPair
* pair
;
3057 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
3061 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
3067 RefPtr
<OriginInfo
> originInfo
=
3068 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
3074 // We need this extra raw pointer because we can't assign to the smart
3075 // pointer directly since QuotaObject::AddRef would try to acquire the same
3077 const NotNull
<CanonicalQuotaObject
*> canonicalQuotaObject
=
3078 originInfo
->mCanonicalQuotaObjects
.LookupOrInsertWith(path
, [&] {
3079 // Create a new QuotaObject. The hashtable is not responsible to
3080 // delete the QuotaObject.
3081 return WrapNotNullUnchecked(new CanonicalQuotaObject(
3082 originInfo
, aClientType
, path
, fileSize
));
3085 // Addref the QuotaObject and move the ownership to the result. This must
3086 // happen before we unlock!
3087 result
= canonicalQuotaObject
->LockedAddRef();
3091 *aFileSizeOut
= fileSize
;
3094 // The caller becomes the owner of the QuotaObject, that is, the caller is
3095 // is responsible to delete it when the last reference is removed.
3096 return result
.forget();
3099 already_AddRefed
<QuotaObject
> QuotaManager::GetQuotaObject(
3100 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
3101 Client::Type aClientType
, const nsAString
& aPath
, int64_t aFileSize
,
3102 int64_t* aFileSizeOut
/* = nullptr */) {
3103 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3109 QM_TRY_INSPECT(const auto& file
, QM_NewLocalFile(aPath
), nullptr);
3111 return GetQuotaObject(aPersistenceType
, aOriginMetadata
, aClientType
, file
,
3112 aFileSize
, aFileSizeOut
);
3115 already_AddRefed
<QuotaObject
> QuotaManager::GetQuotaObject(
3116 const int64_t aDirectoryLockId
, const nsAString
& aPath
) {
3117 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3119 Maybe
<MutexAutoLock
> lock
;
3121 // See the comment for mDirectoryLockIdTable in QuotaManager.h
3122 if (!IsOnBackgroundThread()) {
3123 lock
.emplace(mQuotaMutex
);
3126 if (auto maybeDirectoryLock
=
3127 mDirectoryLockIdTable
.MaybeGet(aDirectoryLockId
)) {
3128 const auto& directoryLock
= *maybeDirectoryLock
;
3129 MOZ_DIAGNOSTIC_ASSERT(directoryLock
->ShouldUpdateLockIdTable());
3131 const PersistenceType persistenceType
= directoryLock
->GetPersistenceType();
3132 const OriginMetadata
& originMetadata
= directoryLock
->OriginMetadata();
3133 const Client::Type clientType
= directoryLock
->ClientType();
3137 return GetQuotaObject(persistenceType
, originMetadata
, clientType
, aPath
);
3140 MOZ_ASSERT(aDirectoryLockId
== -1);
3144 Nullable
<bool> QuotaManager::OriginPersisted(
3145 const OriginMetadata
& aOriginMetadata
) {
3146 AssertIsOnIOThread();
3148 MutexAutoLock
lock(mQuotaMutex
);
3150 RefPtr
<OriginInfo
> originInfo
=
3151 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT
, aOriginMetadata
);
3153 return Nullable
<bool>(originInfo
->LockedPersisted());
3156 return Nullable
<bool>();
3159 void QuotaManager::PersistOrigin(const OriginMetadata
& aOriginMetadata
) {
3160 AssertIsOnIOThread();
3162 MutexAutoLock
lock(mQuotaMutex
);
3164 RefPtr
<OriginInfo
> originInfo
=
3165 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT
, aOriginMetadata
);
3166 if (originInfo
&& !originInfo
->LockedPersisted()) {
3167 originInfo
->LockedPersist();
3171 void QuotaManager::AbortOperationsForLocks(
3172 const DirectoryLockIdTableArray
& aLockIds
) {
3173 for (Client::Type type
: AllClientTypes()) {
3174 if (aLockIds
[type
].Filled()) {
3175 (*mClients
)[type
]->AbortOperationsForLocks(aLockIds
[type
]);
3180 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId
) {
3181 AssertIsOnOwningThread();
3183 for (const RefPtr
<Client
>& client
: *mClients
) {
3184 client
->AbortOperationsForProcess(aContentParentId
);
3188 Result
<nsCOMPtr
<nsIFile
>, nsresult
> QuotaManager::GetOriginDirectory(
3189 const OriginMetadata
& aOriginMetadata
) const {
3192 QM_NewLocalFile(GetStoragePath(aOriginMetadata
.mPersistenceType
)));
3194 QM_TRY(MOZ_TO_RESULT(directory
->Append(
3195 MakeSanitizedOriginString(aOriginMetadata
.mStorageOrigin
))));
3201 nsresult
QuotaManager::CreateDirectoryMetadata(
3202 nsIFile
& aDirectory
, int64_t aTimestamp
,
3203 const OriginMetadata
& aOriginMetadata
) {
3204 AssertIsOnIOThread();
3206 OriginAttributes groupAttributes
;
3208 nsCString groupNoSuffix
;
3209 QM_TRY(OkIf(groupAttributes
.PopulateFromOrigin(aOriginMetadata
.mGroup
,
3213 nsCString groupPrefix
;
3214 GetJarPrefix(groupAttributes
.mInIsolatedMozBrowser
, groupPrefix
);
3216 nsCString group
= groupPrefix
+ groupNoSuffix
;
3218 OriginAttributes originAttributes
;
3220 nsCString originNoSuffix
;
3221 QM_TRY(OkIf(originAttributes
.PopulateFromOrigin(aOriginMetadata
.mOrigin
,
3225 nsCString originPrefix
;
3226 GetJarPrefix(originAttributes
.mInIsolatedMozBrowser
, originPrefix
);
3228 nsCString origin
= originPrefix
+ originNoSuffix
;
3230 MOZ_ASSERT(groupPrefix
== originPrefix
);
3232 QM_TRY_INSPECT(const auto& file
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3233 nsCOMPtr
<nsIFile
>, aDirectory
, Clone
));
3235 QM_TRY(MOZ_TO_RESULT(file
->Append(nsLiteralString(METADATA_TMP_FILE_NAME
))));
3237 QM_TRY_INSPECT(const auto& stream
,
3238 GetBinaryOutputStream(*file
, FileFlag::Truncate
));
3241 QM_TRY(MOZ_TO_RESULT(stream
->Write64(aTimestamp
)));
3243 QM_TRY(MOZ_TO_RESULT(stream
->WriteStringZ(group
.get())));
3245 QM_TRY(MOZ_TO_RESULT(stream
->WriteStringZ(origin
.get())));
3247 // Currently unused (used to be isApp).
3248 QM_TRY(MOZ_TO_RESULT(stream
->WriteBoolean(false)));
3250 QM_TRY(MOZ_TO_RESULT(stream
->Flush()));
3252 QM_TRY(MOZ_TO_RESULT(stream
->Close()));
3254 QM_TRY(MOZ_TO_RESULT(
3255 file
->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME
))));
3261 nsresult
QuotaManager::CreateDirectoryMetadata2(
3262 nsIFile
& aDirectory
, int64_t aTimestamp
, bool aPersisted
,
3263 const OriginMetadata
& aOriginMetadata
) {
3264 AssertIsOnIOThread();
3266 QM_TRY_INSPECT(const auto& file
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3267 nsCOMPtr
<nsIFile
>, aDirectory
, Clone
));
3270 MOZ_TO_RESULT(file
->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME
))));
3272 QM_TRY_INSPECT(const auto& stream
,
3273 GetBinaryOutputStream(*file
, FileFlag::Truncate
));
3276 QM_TRY(MOZ_TO_RESULT(stream
->Write64(aTimestamp
)));
3278 QM_TRY(MOZ_TO_RESULT(stream
->WriteBoolean(aPersisted
)));
3281 QM_TRY(MOZ_TO_RESULT(stream
->Write32(0)));
3284 QM_TRY(MOZ_TO_RESULT(stream
->Write32(0)));
3286 // Currently unused (used to be suffix).
3287 QM_TRY(MOZ_TO_RESULT(stream
->WriteStringZ("")));
3289 // Currently unused (used to be group).
3290 QM_TRY(MOZ_TO_RESULT(stream
->WriteStringZ("")));
3292 QM_TRY(MOZ_TO_RESULT(
3293 stream
->WriteStringZ(aOriginMetadata
.mStorageOrigin
.get())));
3295 // Currently used for isPrivate (used to be used for isApp).
3296 QM_TRY(MOZ_TO_RESULT(stream
->WriteBoolean(aOriginMetadata
.mIsPrivate
)));
3298 QM_TRY(MOZ_TO_RESULT(stream
->Flush()));
3300 QM_TRY(MOZ_TO_RESULT(stream
->Close()));
3302 QM_TRY(MOZ_TO_RESULT(
3303 file
->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME
))));
3308 nsresult
QuotaManager::RestoreDirectoryMetadata2(nsIFile
* aDirectory
) {
3309 AssertIsOnIOThread();
3310 MOZ_ASSERT(aDirectory
);
3311 MOZ_ASSERT(mStorageConnection
);
3313 RefPtr
<RestoreDirectoryMetadata2Helper
> helper
=
3314 new RestoreDirectoryMetadata2Helper(aDirectory
);
3316 QM_TRY(MOZ_TO_RESULT(helper
->Init()));
3318 QM_TRY(MOZ_TO_RESULT(helper
->RestoreMetadata2File()));
3323 Result
<FullOriginMetadata
, nsresult
> QuotaManager::LoadFullOriginMetadata(
3324 nsIFile
* aDirectory
, PersistenceType aPersistenceType
) {
3325 MOZ_ASSERT(!NS_IsMainThread());
3326 MOZ_ASSERT(aDirectory
);
3327 MOZ_ASSERT(mStorageConnection
);
3329 QM_TRY_INSPECT(const auto& binaryStream
,
3330 GetBinaryInputStream(*aDirectory
,
3331 nsLiteralString(METADATA_V2_FILE_NAME
)));
3333 FullOriginMetadata fullOriginMetadata
;
3335 QM_TRY_UNWRAP(fullOriginMetadata
.mLastAccessTime
,
3336 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, Read64
));
3338 QM_TRY_UNWRAP(fullOriginMetadata
.mPersisted
,
3339 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, ReadBoolean
));
3341 QM_TRY_INSPECT(const bool& reservedData1
,
3342 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, Read32
));
3343 Unused
<< reservedData1
;
3345 // XXX Use for the persistence type.
3346 QM_TRY_INSPECT(const bool& reservedData2
,
3347 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, Read32
));
3348 Unused
<< reservedData2
;
3350 fullOriginMetadata
.mPersistenceType
= aPersistenceType
;
3352 QM_TRY_INSPECT(const auto& suffix
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3353 nsCString
, binaryStream
, ReadCString
));
3356 QM_TRY_INSPECT(const auto& group
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3357 nsCString
, binaryStream
, ReadCString
));
3361 fullOriginMetadata
.mStorageOrigin
,
3362 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString
, binaryStream
, ReadCString
));
3364 // Currently used for isPrivate (used to be used for isApp).
3365 QM_TRY_UNWRAP(fullOriginMetadata
.mIsPrivate
,
3366 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, ReadBoolean
));
3368 QM_TRY(MOZ_TO_RESULT(binaryStream
->Close()));
3372 fullOriginMetadata
.mStorageOrigin
]() -> nsCOMPtr
<nsIPrincipal
> {
3373 if (storageOrigin
.EqualsLiteral(kChromeOrigin
)) {
3374 return SystemPrincipal::Get();
3376 return BasePrincipal::CreateContentPrincipal(storageOrigin
);
3378 QM_TRY(MOZ_TO_RESULT(principal
));
3380 PrincipalInfo principalInfo
;
3381 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal
, &principalInfo
)));
3383 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo
)),
3384 Err(NS_ERROR_MALFORMED_URI
));
3386 QM_TRY_UNWRAP(auto principalMetadata
,
3387 GetInfoFromValidatedPrincipalInfo(principalInfo
));
3389 fullOriginMetadata
.mSuffix
= std::move(principalMetadata
.mSuffix
);
3390 fullOriginMetadata
.mGroup
= std::move(principalMetadata
.mGroup
);
3391 fullOriginMetadata
.mOrigin
= std::move(principalMetadata
.mOrigin
);
3393 QM_TRY_INSPECT(const bool& groupUpdated
,
3394 MaybeUpdateGroupForOrigin(fullOriginMetadata
));
3396 // A workaround for a bug in GetLastModifiedTime implementation which should
3397 // have returned the current time instead of INT64_MIN when there were no
3398 // suitable files for getting last modified time.
3399 QM_TRY_INSPECT(const bool& lastAccessTimeUpdated
,
3400 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata
));
3402 if (groupUpdated
|| lastAccessTimeUpdated
) {
3403 // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
3405 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
3406 *aDirectory
, fullOriginMetadata
.mLastAccessTime
,
3407 fullOriginMetadata
.mPersisted
, fullOriginMetadata
)));
3410 return fullOriginMetadata
;
3413 Result
<FullOriginMetadata
, nsresult
>
3414 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile
* aDirectory
) {
3415 // XXX Once the persistence type is stored in the metadata file, this block
3416 // for getting the persistence type from the parent directory name can be
3418 nsCOMPtr
<nsIFile
> parentDir
;
3419 QM_TRY(MOZ_TO_RESULT(aDirectory
->GetParent(getter_AddRefs(parentDir
))));
3421 const auto maybePersistenceType
=
3422 PersistenceTypeFromFile(*parentDir
, fallible
);
3423 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
3425 const auto& persistenceType
= maybePersistenceType
.value();
3427 QM_TRY_RETURN(QM_OR_ELSE_WARN(
3429 LoadFullOriginMetadata(aDirectory
, persistenceType
),
3431 ([&aDirectory
, &persistenceType
,
3432 this](const nsresult rv
) -> Result
<FullOriginMetadata
, nsresult
> {
3433 QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory
)));
3435 QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory
, persistenceType
));
3439 Result
<OriginMetadata
, nsresult
> QuotaManager::GetOriginMetadata(
3440 nsIFile
* aDirectory
) {
3441 MOZ_ASSERT(aDirectory
);
3442 MOZ_ASSERT(mStorageConnection
);
3445 const auto& leafName
,
3446 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString
, aDirectory
, GetLeafName
));
3448 // XXX Consider using QuotaManager::ParseOrigin here.
3450 OriginAttributes attrs
;
3451 nsCString originalSuffix
;
3452 OriginParser::ResultType result
= OriginParser::ParseOrigin(
3453 NS_ConvertUTF16toUTF8(leafName
), spec
, &attrs
, originalSuffix
);
3454 QM_TRY(MOZ_TO_RESULT(result
== OriginParser::ValidOrigin
));
3457 const auto& principal
,
3458 ([&spec
, &attrs
]() -> Result
<nsCOMPtr
<nsIPrincipal
>, nsresult
> {
3459 if (spec
.EqualsLiteral(kChromeOrigin
)) {
3460 return nsCOMPtr
<nsIPrincipal
>(SystemPrincipal::Get());
3463 nsCOMPtr
<nsIURI
> uri
;
3464 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri
), spec
)));
3466 return nsCOMPtr
<nsIPrincipal
>(
3467 BasePrincipal::CreateContentPrincipal(uri
, attrs
));
3469 QM_TRY(MOZ_TO_RESULT(principal
));
3471 PrincipalInfo principalInfo
;
3472 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal
, &principalInfo
)));
3474 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo
)),
3475 Err(NS_ERROR_MALFORMED_URI
));
3477 QM_TRY_UNWRAP(auto principalMetadata
,
3478 GetInfoFromValidatedPrincipalInfo(principalInfo
));
3480 QM_TRY_INSPECT(const auto& parentDirectory
,
3481 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr
<nsIFile
>,
3482 aDirectory
, GetParent
));
3484 const auto maybePersistenceType
=
3485 PersistenceTypeFromFile(*parentDirectory
, fallible
);
3486 QM_TRY(MOZ_TO_RESULT(maybePersistenceType
.isSome()));
3488 return OriginMetadata
{std::move(principalMetadata
),
3489 maybePersistenceType
.value()};
3492 Result
<Ok
, nsresult
> QuotaManager::RemoveOriginDirectory(nsIFile
& aDirectory
) {
3493 AssertIsOnIOThread();
3495 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown
)) {
3496 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory
.Remove(true)));
3499 QM_TRY_INSPECT(const auto& toBeRemovedStorageDir
,
3500 QM_NewLocalFile(*mToBeRemovedStoragePath
));
3502 QM_TRY_INSPECT(const bool& created
, EnsureDirectory(*toBeRemovedStorageDir
));
3506 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory
.MoveTo(
3507 toBeRemovedStorageDir
, NSID_TrimBracketsUTF16(nsID::GenerateUUID()))));
3510 template <typename OriginFunc
>
3511 nsresult
QuotaManager::InitializeRepository(PersistenceType aPersistenceType
,
3512 OriginFunc
&& aOriginFunc
) {
3513 AssertIsOnIOThread();
3514 MOZ_ASSERT(aPersistenceType
== PERSISTENCE_TYPE_TEMPORARY
||
3515 aPersistenceType
== PERSISTENCE_TYPE_DEFAULT
);
3517 QM_TRY_INSPECT(const auto& directory
,
3518 QM_NewLocalFile(GetStoragePath(aPersistenceType
)));
3520 QM_TRY_INSPECT(const bool& created
, EnsureDirectory(*directory
));
3524 // A keeper to defer the return only in Nightly, so that the telemetry data
3525 // for whole profile can be collected
3526 #ifdef NIGHTLY_BUILD
3527 nsresult statusKeeper
= NS_OK
;
3530 const auto statusKeeperFunc
= [&](const nsresult rv
) {
3531 RECORD_IN_NIGHTLY(statusKeeper
, rv
);
3534 struct RenameAndInitInfo
{
3535 nsCOMPtr
<nsIFile
> mOriginDirectory
;
3536 FullOriginMetadata mFullOriginMetadata
;
3540 nsTArray
<RenameAndInitInfo
> renameAndInitInfos
;
3542 QM_TRY(([&]() -> Result
<Ok
, nsresult
> {
3546 [&](nsCOMPtr
<nsIFile
>&& childDirectory
) -> Result
<Ok
, nsresult
> {
3547 if (NS_WARN_IF(IsShuttingDown())) {
3548 RETURN_STATUS_OR_RESULT(statusKeeper
, NS_ERROR_ABORT
);
3552 ([this, &childDirectory
, &renameAndInitInfos
,
3553 aPersistenceType
, &aOriginFunc
]() -> Result
<Ok
, nsresult
> {
3555 const auto& leafName
,
3556 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3557 nsAutoString
, childDirectory
, GetLeafName
));
3559 QM_TRY_INSPECT(const auto& dirEntryKind
,
3560 GetDirEntryKind(*childDirectory
));
3562 switch (dirEntryKind
) {
3563 case nsIFileKind::ExistsAsDirectory
: {
3568 LoadFullOriginMetadataWithRestore(
3570 .map([](auto metadata
)
3571 -> Maybe
<FullOriginMetadata
> {
3572 return Some(std::move(metadata
));
3575 IsSpecificError
<NS_ERROR_MALFORMED_URI
>,
3577 ErrToDefaultOk
<Maybe
<FullOriginMetadata
>>));
3579 if (!maybeMetadata
) {
3580 // Unknown directories during initialization are
3581 // allowed. Just warn if we find them.
3582 UNKNOWN_FILE_WARNING(leafName
);
3586 auto metadata
= maybeMetadata
.extract();
3588 MOZ_ASSERT(metadata
.mPersistenceType
==
3591 // FIXME(tt): The check for origin name consistency can
3592 // be removed once we have an upgrade to traverse origin
3593 // directories and check through the directory metadata
3595 const auto originSanitized
=
3596 MakeSanitizedOriginCString(metadata
.mOrigin
);
3598 NS_ConvertUTF16toUTF8
utf8LeafName(leafName
);
3599 if (!originSanitized
.Equals(utf8LeafName
)) {
3601 "The name of the origin directory (%s) doesn't "
3602 "match the sanitized origin string (%s) in the "
3604 utf8LeafName
.get(), originSanitized
.get());
3606 // If it's the known case, we try to restore the
3607 // origin directory name if it's possible.
3608 if (originSanitized
.Equals(utf8LeafName
+ "."_ns
)) {
3609 const int64_t lastAccessTime
=
3610 metadata
.mLastAccessTime
;
3611 const bool persisted
= metadata
.mPersisted
;
3612 renameAndInitInfos
.AppendElement(RenameAndInitInfo
{
3613 std::move(childDirectory
), std::move(metadata
),
3614 lastAccessTime
, persisted
});
3618 // XXXtt: Try to restore the unknown cases base on the
3619 // content for their metadata files. Note that if the
3620 // restore fails, QM should maintain a list and ensure
3621 // they won't be accessed after initialization.
3624 QM_TRY(QM_OR_ELSE_WARN_IF(
3626 MOZ_TO_RESULT(InitializeOrigin(
3627 aPersistenceType
, metadata
,
3628 metadata
.mLastAccessTime
, metadata
.mPersisted
,
3631 IsDatabaseCorruptionError
,
3634 const nsresult rv
) -> Result
<Ok
, nsresult
> {
3635 // If the origin can't be initialized due to
3636 // corruption, this is a permanent
3637 // condition, and we need to remove all data
3638 // for the origin on disk.
3641 MOZ_TO_RESULT(childDirectory
->Remove(true)));
3646 std::forward
<OriginFunc
>(aOriginFunc
)(metadata
);
3651 case nsIFileKind::ExistsAsFile
:
3652 if (IsOSMetadata(leafName
) || IsDotFile(leafName
)) {
3656 // Unknown files during initialization are now allowed.
3657 // Just warn if we find them.
3658 UNKNOWN_FILE_WARNING(leafName
);
3661 case nsIFileKind::DoesNotExist
:
3662 // Ignore files that got removed externally while
3669 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
3673 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
3678 for (auto& info
: renameAndInitInfos
) {
3679 QM_TRY(([&]() -> Result
<Ok
, nsresult
> {
3680 QM_TRY(([&directory
, &info
, this, aPersistenceType
,
3681 &aOriginFunc
]() -> Result
<Ok
, nsresult
> {
3682 const auto originDirName
=
3683 MakeSanitizedOriginString(info
.mFullOriginMetadata
.mOrigin
);
3685 // Check if targetDirectory exist.
3686 QM_TRY_INSPECT(const auto& targetDirectory
,
3687 CloneFileAndAppend(*directory
, originDirName
));
3689 QM_TRY_INSPECT(const bool& exists
, MOZ_TO_RESULT_INVOKE_MEMBER(
3690 targetDirectory
, Exists
));
3693 QM_TRY(MOZ_TO_RESULT(info
.mOriginDirectory
->Remove(true)));
3698 QM_TRY(MOZ_TO_RESULT(
3699 info
.mOriginDirectory
->RenameTo(nullptr, originDirName
)));
3701 // XXX We don't check corruption here ?
3702 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
3703 aPersistenceType
, info
.mFullOriginMetadata
, info
.mTimestamp
,
3704 info
.mPersisted
, targetDirectory
)));
3706 std::forward
<OriginFunc
>(aOriginFunc
)(info
.mFullOriginMetadata
);
3710 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
3716 #ifdef NIGHTLY_BUILD
3717 if (NS_FAILED(statusKeeper
)) {
3718 return statusKeeper
;
3725 nsresult
QuotaManager::InitializeOrigin(PersistenceType aPersistenceType
,
3726 const OriginMetadata
& aOriginMetadata
,
3727 int64_t aAccessTime
, bool aPersisted
,
3728 nsIFile
* aDirectory
) {
3729 AssertIsOnIOThread();
3731 const bool trackQuota
= aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
;
3733 // We need to initialize directories of all clients if they exists and also
3734 // get the total usage to initialize the quota.
3736 ClientUsageArray clientUsages
;
3738 // A keeper to defer the return only in Nightly, so that the telemetry data
3739 // for whole profile can be collected
3740 #ifdef NIGHTLY_BUILD
3741 nsresult statusKeeper
= NS_OK
;
3744 QM_TRY(([&, statusKeeperFunc
= [&](const nsresult rv
) {
3745 RECORD_IN_NIGHTLY(statusKeeper
, rv
);
3746 }]() -> Result
<Ok
, nsresult
> {
3750 [&](const nsCOMPtr
<nsIFile
>& file
) -> Result
<Ok
, nsresult
> {
3751 if (NS_WARN_IF(IsShuttingDown())) {
3752 RETURN_STATUS_OR_RESULT(statusKeeper
, NS_ERROR_ABORT
);
3756 ([this, &file
, trackQuota
, aPersistenceType
, &aOriginMetadata
,
3757 &clientUsages
]() -> Result
<Ok
, nsresult
> {
3758 QM_TRY_INSPECT(const auto& leafName
,
3759 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3760 nsAutoString
, file
, GetLeafName
));
3762 QM_TRY_INSPECT(const auto& dirEntryKind
,
3763 GetDirEntryKind(*file
));
3765 switch (dirEntryKind
) {
3766 case nsIFileKind::ExistsAsDirectory
: {
3767 Client::Type clientType
;
3768 const bool ok
= Client::TypeFromText(
3769 leafName
, clientType
, fallible
);
3771 // Unknown directories during initialization are now
3772 // allowed. Just warn if we find them.
3773 UNKNOWN_FILE_WARNING(leafName
);
3779 const auto& usageInfo
,
3780 (*mClients
)[clientType
]->InitOrigin(
3781 aPersistenceType
, aOriginMetadata
,
3782 /* aCanceled */ Atomic
<bool>(false)));
3784 MOZ_ASSERT(!clientUsages
[clientType
]);
3786 if (usageInfo
.TotalUsage()) {
3787 // XXX(Bug 1683863) Until we identify the root cause
3788 // of seemingly converted-from-negative usage
3789 // values, we will just treat them as unset here,
3790 // but log a warning to the browser console.
3791 if (static_cast<int64_t>(*usageInfo
.TotalUsage()) >=
3793 clientUsages
[clientType
] = usageInfo
.TotalUsage();
3795 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
3796 const nsCOMPtr
<nsIConsoleService
> console
=
3797 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
3799 console
->LogStringMessage(
3801 u
"QuotaManager warning: client "_ns
+
3803 u
" reported negative usage for group "_ns
+
3804 NS_ConvertUTF8toUTF16(
3805 aOriginMetadata
.mGroup
) +
3807 NS_ConvertUTF8toUTF16(
3808 aOriginMetadata
.mOrigin
))
3815 QM_TRY(MOZ_TO_RESULT(
3816 (*mClients
)[clientType
]
3817 ->InitOriginWithoutTracking(
3818 aPersistenceType
, aOriginMetadata
,
3819 /* aCanceled */ Atomic
<bool>(false))));
3825 case nsIFileKind::ExistsAsFile
:
3826 if (IsOriginMetadata(leafName
)) {
3830 if (IsTempMetadata(leafName
)) {
3831 QM_TRY(MOZ_TO_RESULT(
3832 file
->Remove(/* recursive */ false)));
3837 if (IsOSMetadata(leafName
) || IsDotFile(leafName
)) {
3841 // Unknown files during initialization are now allowed.
3842 // Just warn if we find them.
3843 UNKNOWN_FILE_WARNING(leafName
);
3844 // Bug 1595448 will handle the case for unknown files
3845 // like idb, cache, or ls.
3848 case nsIFileKind::DoesNotExist
:
3849 // Ignore files that got removed externally while
3856 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
3860 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS
, statusKeeperFunc
);
3865 #ifdef NIGHTLY_BUILD
3866 if (NS_FAILED(statusKeeper
)) {
3867 return statusKeeper
;
3872 const auto usage
= std::accumulate(
3873 clientUsages
.cbegin(), clientUsages
.cend(), CheckedUint64(0),
3874 [](CheckedUint64 value
, const Maybe
<uint64_t>& clientUsage
) {
3875 return value
+ clientUsage
.valueOr(0);
3878 // XXX Should we log more information, i.e. the whole clientUsages array, in
3879 // case usage is not valid?
3881 QM_TRY(OkIf(usage
.isValid()), NS_ERROR_FAILURE
);
3884 FullOriginMetadata
{aOriginMetadata
, aPersisted
, aAccessTime
},
3885 clientUsages
, usage
.value());
3892 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
3893 nsIFile
* aIndexedDBDir
) {
3894 AssertIsOnIOThread();
3895 MOZ_ASSERT(aIndexedDBDir
);
3897 const auto innerFunc
= [this, &aIndexedDBDir
](const auto&) -> nsresult
{
3899 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir
->IsDirectory(&isDirectory
)));
3902 NS_WARNING("indexedDB entry is not a directory!");
3906 auto persistentStorageDirOrErr
= QM_NewLocalFile(*mStoragePath
);
3907 if (NS_WARN_IF(persistentStorageDirOrErr
.isErr())) {
3908 return persistentStorageDirOrErr
.unwrapErr();
3911 nsCOMPtr
<nsIFile
> persistentStorageDir
= persistentStorageDirOrErr
.unwrap();
3913 QM_TRY(MOZ_TO_RESULT(persistentStorageDir
->Append(
3914 nsLiteralString(PERSISTENT_DIRECTORY_NAME
))));
3917 QM_TRY(MOZ_TO_RESULT(persistentStorageDir
->Exists(&exists
)));
3920 QM_WARNING("Deleting old <profile>/indexedDB directory!");
3922 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir
->Remove(/* aRecursive */ true)));
3927 nsCOMPtr
<nsIFile
> storageDir
;
3928 QM_TRY(MOZ_TO_RESULT(
3929 persistentStorageDir
->GetParent(getter_AddRefs(storageDir
))));
3931 // MoveTo() is atomic if the move happens on the same volume which should
3932 // be our case, so even if we crash in the middle of the operation nothing
3933 // breaks next time we try to initialize.
3934 // However there's a theoretical possibility that the indexedDB directory
3935 // is on different volume, but it should be rare enough that we don't have
3936 // to worry about it.
3937 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir
->MoveTo(
3938 storageDir
, nsLiteralString(PERSISTENT_DIRECTORY_NAME
))));
3943 return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory
,
3948 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
3949 nsIFile
* aPersistentStorageDir
) {
3950 AssertIsOnIOThread();
3951 MOZ_ASSERT(aPersistentStorageDir
);
3953 const auto innerFunc
= [this,
3954 &aPersistentStorageDir
](const auto&) -> nsresult
{
3956 const bool& isDirectory
,
3957 MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir
, IsDirectory
));
3960 NS_WARNING("persistent entry is not a directory!");
3965 QM_TRY_INSPECT(const auto& defaultStorageDir
,
3966 QM_NewLocalFile(*mDefaultStoragePath
));
3968 QM_TRY_INSPECT(const bool& exists
,
3969 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir
, Exists
));
3972 QM_WARNING("Deleting old <profile>/storage/persistent directory!");
3974 QM_TRY(MOZ_TO_RESULT(
3975 aPersistentStorageDir
->Remove(/* aRecursive */ true)));
3982 // Create real metadata files for origin directories in persistent
3984 auto helper
= MakeRefPtr
<CreateOrUpgradeDirectoryMetadataHelper
>(
3985 aPersistentStorageDir
);
3987 QM_TRY(MOZ_TO_RESULT(helper
->Init()));
3989 QM_TRY(MOZ_TO_RESULT(helper
->ProcessRepository()));
3991 // Upgrade metadata files for origin directories in temporary storage.
3992 QM_TRY_INSPECT(const auto& temporaryStorageDir
,
3993 QM_NewLocalFile(*mTemporaryStoragePath
));
3995 QM_TRY_INSPECT(const bool& exists
,
3996 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir
, Exists
));
4000 const bool& isDirectory
,
4001 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir
, IsDirectory
));
4004 NS_WARNING("temporary entry is not a directory!");
4008 helper
= MakeRefPtr
<CreateOrUpgradeDirectoryMetadataHelper
>(
4009 temporaryStorageDir
);
4011 QM_TRY(MOZ_TO_RESULT(helper
->Init()));
4013 QM_TRY(MOZ_TO_RESULT(helper
->ProcessRepository()));
4017 // And finally rename persistent to default.
4018 QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir
->RenameTo(
4019 nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME
))));
4024 return ExecuteInitialization(
4025 Initialization::UpgradeFromPersistentStorageDirectory
, innerFunc
);
4028 template <typename Helper
>
4029 nsresult
QuotaManager::UpgradeStorage(const int32_t aOldVersion
,
4030 const int32_t aNewVersion
,
4031 mozIStorageConnection
* aConnection
) {
4032 AssertIsOnIOThread();
4033 MOZ_ASSERT(aNewVersion
> aOldVersion
);
4034 MOZ_ASSERT(aNewVersion
<= kStorageVersion
);
4035 MOZ_ASSERT(aConnection
);
4037 for (const PersistenceType persistenceType
: kAllPersistenceTypes
) {
4038 QM_TRY_UNWRAP(auto directory
,
4039 QM_NewLocalFile(GetStoragePath(persistenceType
)));
4041 QM_TRY_INSPECT(const bool& exists
,
4042 MOZ_TO_RESULT_INVOKE_MEMBER(directory
, Exists
));
4048 RefPtr
<UpgradeStorageHelperBase
> helper
= new Helper(directory
);
4050 QM_TRY(MOZ_TO_RESULT(helper
->Init()));
4052 QM_TRY(MOZ_TO_RESULT(helper
->ProcessRepository()));
4057 QM_TRY_INSPECT(const int32_t& storageVersion
,
4058 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection
, GetSchemaVersion
));
4060 MOZ_ASSERT(storageVersion
== aOldVersion
);
4064 QM_TRY(MOZ_TO_RESULT(aConnection
->SetSchemaVersion(aNewVersion
)));
4069 nsresult
QuotaManager::UpgradeStorageFrom0_0To1_0(
4070 mozIStorageConnection
* aConnection
) {
4071 AssertIsOnIOThread();
4072 MOZ_ASSERT(aConnection
);
4074 const auto innerFunc
= [this, &aConnection
](const auto&) -> nsresult
{
4075 QM_TRY(MOZ_TO_RESULT(UpgradeStorage
<UpgradeStorageFrom0_0To1_0Helper
>(
4076 0, MakeStorageVersion(1, 0), aConnection
)));
4081 return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0
,
4085 nsresult
QuotaManager::UpgradeStorageFrom1_0To2_0(
4086 mozIStorageConnection
* aConnection
) {
4087 AssertIsOnIOThread();
4088 MOZ_ASSERT(aConnection
);
4090 // The upgrade consists of a number of logically distinct bugs that
4091 // intentionally got fixed at the same time to trigger just one major
4095 // Morgue directory cleanup
4097 // The original bug that added "on demand" morgue cleanup is 1165119.
4100 // Morgue directories are removed from all origin directories during the
4101 // upgrade process. Origin initialization and usage calculation doesn't try
4102 // to remove morgue directories anymore.
4104 // [Downgrade-incompatible changes]:
4105 // Morgue directories can reappear if user runs an already upgraded profile
4106 // in an older version of Firefox. Morgue directories then prevent current
4107 // Firefox from initializing and using the storage.
4112 // The bug that removes isApp flags is 1311057.
4115 // Origin directories with appIds are removed during the upgrade process.
4117 // [Downgrade-incompatible changes]:
4118 // Origin directories with appIds can reappear if user runs an already
4119 // upgraded profile in an older version of Firefox. Origin directories with
4120 // appIds don't prevent current Firefox from initializing and using the
4121 // storage, but they wouldn't ever be removed again, potentially causing
4122 // problems once appId is removed from origin attributes.
4125 // Strip obsolete origin attributes
4127 // The bug that strips obsolete origin attributes is 1314361.
4130 // Origin directories with obsolete origin attributes are renamed and their
4131 // metadata files are updated during the upgrade process.
4133 // [Downgrade-incompatible changes]:
4134 // Origin directories with obsolete origin attributes can reappear if user
4135 // runs an already upgraded profile in an older version of Firefox. Origin
4136 // directories with obsolete origin attributes don't prevent current Firefox
4137 // from initializing and using the storage, but they wouldn't ever be upgraded
4138 // again, potentially causing problems in future.
4141 // File manager directory renaming (client specific)
4143 // The original bug that added "on demand" file manager directory renaming is
4147 // All file manager directories are renamed to contain the ".files" suffix.
4149 // [Downgrade-incompatible changes]:
4150 // File manager directories with the ".files" suffix prevent older versions of
4151 // Firefox from initializing and using the storage.
4152 // File manager directories without the ".files" suffix can appear if user
4153 // runs an already upgraded profile in an older version of Firefox. File
4154 // manager directories without the ".files" suffix then prevent current
4155 // Firefox from initializing and using the storage.
4157 const auto innerFunc
= [this, &aConnection
](const auto&) -> nsresult
{
4158 QM_TRY(MOZ_TO_RESULT(UpgradeStorage
<UpgradeStorageFrom1_0To2_0Helper
>(
4159 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection
)));
4164 return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0
,
4168 nsresult
QuotaManager::UpgradeStorageFrom2_0To2_1(
4169 mozIStorageConnection
* aConnection
) {
4170 AssertIsOnIOThread();
4171 MOZ_ASSERT(aConnection
);
4173 // The upgrade is mainly to create a directory padding file in DOM Cache
4174 // directory to record the overall padding size of an origin.
4176 const auto innerFunc
= [this, &aConnection
](const auto&) -> nsresult
{
4177 QM_TRY(MOZ_TO_RESULT(UpgradeStorage
<UpgradeStorageFrom2_0To2_1Helper
>(
4178 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection
)));
4183 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1
,
4187 nsresult
QuotaManager::UpgradeStorageFrom2_1To2_2(
4188 mozIStorageConnection
* aConnection
) {
4189 AssertIsOnIOThread();
4190 MOZ_ASSERT(aConnection
);
4192 // The upgrade is mainly to clean obsolete origins in the repositoies, remove
4193 // asmjs client, and ".tmp" file in the idb folers.
4195 const auto innerFunc
= [this, &aConnection
](const auto&) -> nsresult
{
4196 QM_TRY(MOZ_TO_RESULT(UpgradeStorage
<UpgradeStorageFrom2_1To2_2Helper
>(
4197 MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection
)));
4202 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2
,
4206 nsresult
QuotaManager::UpgradeStorageFrom2_2To2_3(
4207 mozIStorageConnection
* aConnection
) {
4208 AssertIsOnIOThread();
4209 MOZ_ASSERT(aConnection
);
4211 const auto innerFunc
= [&aConnection
](const auto&) -> nsresult
{
4213 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteSimpleSQL(
4214 nsLiteralCString("CREATE TABLE database"
4215 "( cache_version INTEGER NOT NULL DEFAULT 0"
4218 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteSimpleSQL(
4219 nsLiteralCString("INSERT INTO database (cache_version) "
4225 const int32_t& storageVersion
,
4226 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection
, GetSchemaVersion
));
4228 MOZ_ASSERT(storageVersion
== MakeStorageVersion(2, 2));
4233 MOZ_TO_RESULT(aConnection
->SetSchemaVersion(MakeStorageVersion(2, 3))));
4238 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3
,
4242 nsresult
QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
4243 nsIFile
& aLsArchiveFile
) {
4244 AssertIsOnIOThread();
4245 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
4247 QM_TRY_INSPECT(const bool& exists
,
4248 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile
, Exists
));
4251 // If the ls archive doesn't exist then ls directories can't exist either.
4255 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4257 InvalidateQuotaCache();
4259 // Finally remove the ls archive, so we don't have to check all origin
4260 // directories next time this method is called.
4261 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile
.Remove(false)));
4266 nsresult
QuotaManager::MaybeRemoveLocalStorageDirectories() {
4267 AssertIsOnIOThread();
4269 QM_TRY_INSPECT(const auto& defaultStorageDir
,
4270 QM_NewLocalFile(*mDefaultStoragePath
));
4272 QM_TRY_INSPECT(const bool& exists
,
4273 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir
, Exists
));
4279 QM_TRY(CollectEachFile(
4281 [](const nsCOMPtr
<nsIFile
>& originDir
) -> Result
<Ok
, nsresult
> {
4284 QM_TRY_INSPECT(const bool& exists
,
4285 MOZ_TO_RESULT_INVOKE_MEMBER(originDir
, Exists
));
4290 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*originDir
));
4292 switch (dirEntryKind
) {
4293 case nsIFileKind::ExistsAsDirectory
: {
4296 CloneFileAndAppend(*originDir
, NS_LITERAL_STRING_FROM_CSTRING(
4297 LS_DIRECTORY_NAME
)));
4300 QM_TRY_INSPECT(const bool& exists
,
4301 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir
, Exists
));
4309 QM_TRY_INSPECT(const bool& isDirectory
,
4310 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir
, IsDirectory
));
4313 QM_WARNING("ls entry is not a directory!");
4320 QM_TRY(MOZ_TO_RESULT(lsDir
->GetPath(path
)));
4322 QM_WARNING("Deleting %s directory!",
4323 NS_ConvertUTF16toUTF8(path
).get());
4325 QM_TRY(MOZ_TO_RESULT(lsDir
->Remove(/* aRecursive */ true)));
4330 case nsIFileKind::ExistsAsFile
: {
4331 QM_TRY_INSPECT(const auto& leafName
,
4332 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4333 nsAutoString
, originDir
, GetLeafName
));
4335 // Unknown files during upgrade are allowed. Just warn if we find
4337 if (!IsOSMetadata(leafName
)) {
4338 UNKNOWN_FILE_WARNING(leafName
);
4344 case nsIFileKind::DoesNotExist
:
4345 // Ignore files that got removed externally while iterating.
4354 Result
<Ok
, nsresult
> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
4355 nsIFile
& aLsArchiveFile
) const {
4356 AssertIsOnIOThread();
4357 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4361 QM_TRY_INSPECT(const bool& exists
,
4362 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile
, Exists
));
4363 MOZ_ASSERT(!exists
);
4367 // Get the storage service first, we will need it at multiple places.
4368 QM_TRY_INSPECT(const auto& ss
,
4369 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr
<mozIStorageService
>,
4370 MOZ_SELECT_OVERLOAD(do_GetService
),
4371 MOZ_STORAGE_SERVICE_CONTRACTID
));
4373 // Get the web apps store file.
4374 QM_TRY_INSPECT(const auto& webAppsStoreFile
, QM_NewLocalFile(mBasePath
));
4376 QM_TRY(MOZ_TO_RESULT(
4377 webAppsStoreFile
->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME
))));
4379 // Now check if the web apps store is useable.
4380 QM_TRY_INSPECT(const auto& connection
,
4381 CreateWebAppsStoreConnection(*webAppsStoreFile
, *ss
));
4384 // Find out the journal mode.
4385 QM_TRY_INSPECT(const auto& stmt
,
4386 CreateAndExecuteSingleStepStatement(
4387 *connection
, "PRAGMA journal_mode;"_ns
));
4389 QM_TRY_INSPECT(const auto& journalMode
,
4390 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString
, *stmt
,
4393 QM_TRY(MOZ_TO_RESULT(stmt
->Finalize()));
4395 if (journalMode
.EqualsLiteral("wal")) {
4396 // We don't copy the WAL file, so make sure the old database is fully
4398 QM_TRY(MOZ_TO_RESULT(
4399 connection
->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns
)));
4402 // Explicitely close the connection before the old database is copied.
4403 QM_TRY(MOZ_TO_RESULT(connection
->Close()));
4405 // Copy the old database. The database is copied from
4406 // <profile>/webappsstore.sqlite to
4407 // <profile>/storage/ls-archive-tmp.sqlite
4408 // We use a "-tmp" postfix since we are not done yet.
4409 QM_TRY_INSPECT(const auto& storageDir
, QM_NewLocalFile(*mStoragePath
));
4411 QM_TRY(MOZ_TO_RESULT(webAppsStoreFile
->CopyTo(
4412 storageDir
, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME
))));
4414 QM_TRY_INSPECT(const auto& lsArchiveTmpFile
,
4415 GetLocalStorageArchiveTmpFile(*mStoragePath
));
4417 if (journalMode
.EqualsLiteral("wal")) {
4419 const auto& lsArchiveTmpConnection
,
4420 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4421 nsCOMPtr
<mozIStorageConnection
>, ss
, OpenUnsharedDatabase
,
4422 lsArchiveTmpFile
, mozIStorageService::CONNECTION_DEFAULT
));
4424 // The archive will only be used for lazy data migration. There won't be
4425 // any concurrent readers and writers that could benefit from Write-Ahead
4426 // Logging. So switch to a standard rollback journal. The standard
4427 // rollback journal also provides atomicity across multiple attached
4428 // databases which is import for the lazy data migration to work safely.
4429 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection
->ExecuteSimpleSQL(
4430 "PRAGMA journal_mode = DELETE;"_ns
)));
4432 // Close the connection explicitly. We are going to rename the file below.
4433 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection
->Close()));
4436 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
4437 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile
->MoveTo(
4438 nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME
))));
4443 // If webappsstore database is not useable, just create an empty archive.
4444 // XXX The code below should be removed and the caller should call us only
4445 // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
4446 // reworked to propagate database corruption instead of returning null
4448 // So, if there's no webappsstore.sqlite
4449 // MaybeCreateOrUpgradeLocalStorageArchive will call
4450 // CreateEmptyLocalStorageArchive instead of
4451 // CopyLocalStorageArchiveFromWebAppsStore.
4452 // If there's any corruption detected during
4453 // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
4454 // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
4455 // EnsureStorageIsInitializedInternal will fallback to
4456 // CreateEmptyLocalStorageArchive.
4458 // Ensure the storage directory actually exists.
4459 QM_TRY_INSPECT(const auto& storageDirectory
, QM_NewLocalFile(*mStoragePath
));
4461 QM_TRY_INSPECT(const bool& created
, EnsureDirectory(*storageDirectory
));
4465 QM_TRY_UNWRAP(auto lsArchiveConnection
,
4466 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4467 nsCOMPtr
<mozIStorageConnection
>, ss
, OpenUnsharedDatabase
,
4468 &aLsArchiveFile
, mozIStorageService::CONNECTION_DEFAULT
));
4470 QM_TRY(MOZ_TO_RESULT(
4471 StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection
)));
4476 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
4477 QuotaManager::CreateLocalStorageArchiveConnection(
4478 nsIFile
& aLsArchiveFile
) const {
4479 AssertIsOnIOThread();
4480 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4484 QM_TRY_INSPECT(const bool& exists
,
4485 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile
, Exists
));
4490 QM_TRY_INSPECT(const bool& isDirectory
,
4491 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile
, IsDirectory
));
4493 // A directory with the name of the archive file is treated as corruption
4494 // (similarly as wrong content of the file).
4495 QM_TRY(OkIf(!isDirectory
), Err(NS_ERROR_FILE_CORRUPTED
));
4497 QM_TRY_INSPECT(const auto& ss
,
4498 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr
<mozIStorageService
>,
4499 MOZ_SELECT_OVERLOAD(do_GetService
),
4500 MOZ_STORAGE_SERVICE_CONTRACTID
));
4502 // This may return NS_ERROR_FILE_CORRUPTED too.
4503 QM_TRY_UNWRAP(auto connection
,
4504 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4505 nsCOMPtr
<mozIStorageConnection
>, ss
, OpenUnsharedDatabase
,
4506 &aLsArchiveFile
, mozIStorageService::CONNECTION_DEFAULT
));
4508 // The legacy LS implementation removes the database and creates an empty one
4509 // when the schema can't be updated. The same effect can be achieved here by
4510 // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
4511 // sub test case 3 of dom/localstorage/test/unit/test_archive.js
4513 MOZ_TO_RESULT(StorageDBUpdater::Update(connection
))
4514 .mapErr([](const nsresult rv
) { return NS_ERROR_FILE_CORRUPTED
; }));
4519 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
4520 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
4521 nsIFile
& aLsArchiveFile
) {
4522 AssertIsOnIOThread();
4523 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4525 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4529 QM_TRY_INSPECT(const bool& exists
,
4530 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile
, Exists
));
4536 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile
.Remove(false)));
4538 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
4540 QM_TRY_UNWRAP(auto connection
,
4541 CreateLocalStorageArchiveConnection(aLsArchiveFile
));
4543 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection
)));
4548 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
4549 QuotaManager::DowngradeLocalStorageArchive(nsIFile
& aLsArchiveFile
) {
4550 AssertIsOnIOThread();
4551 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4553 QM_TRY_UNWRAP(auto connection
,
4554 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
4556 QM_TRY(MOZ_TO_RESULT(
4557 SaveLocalStorageArchiveVersion(connection
, kLocalStorageArchiveVersion
)));
4562 Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
>
4563 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
4564 nsIFile
& aLsArchiveFile
) {
4565 AssertIsOnIOThread();
4566 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4568 QM_TRY_UNWRAP(auto connection
,
4569 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
4571 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection
, 4)));
4577 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
4578 nsCOMPtr<mozIStorageConnection>& aConnection) {
4579 AssertIsOnIOThread();
4580 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4582 nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
4583 if (NS_WARN_IF(NS_FAILED(rv))) {
4593 void QuotaManager::AssertStorageIsInitializedInternal() const {
4594 AssertIsOnIOThread();
4595 MOZ_ASSERT(IsStorageInitializedInternal());
4600 nsresult
QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
4601 nsIFile
& aStorageFile
) {
4602 AssertIsOnIOThread();
4604 QM_TRY_INSPECT(const auto& storageFileExists
,
4605 MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile
, Exists
));
4607 if (!storageFileExists
) {
4608 QM_TRY_INSPECT(const auto& indexedDBDir
, QM_NewLocalFile(*mIndexedDBPath
));
4610 QM_TRY_INSPECT(const auto& indexedDBDirExists
,
4611 MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir
, Exists
));
4613 if (indexedDBDirExists
) {
4614 QM_TRY(MOZ_TO_RESULT(
4615 UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
4619 QM_TRY_INSPECT(const auto& persistentStorageDir
,
4620 QM_NewLocalFile(*mStoragePath
));
4622 QM_TRY(MOZ_TO_RESULT(persistentStorageDir
->Append(
4623 nsLiteralString(PERSISTENT_DIRECTORY_NAME
))));
4625 QM_TRY_INSPECT(const auto& persistentStorageDirExists
,
4626 MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir
, Exists
));
4628 if (persistentStorageDirExists
) {
4629 QM_TRY(MOZ_TO_RESULT(
4630 UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
4631 persistentStorageDir
)));
4638 nsresult
QuotaManager::MaybeCreateOrUpgradeStorage(
4639 mozIStorageConnection
& aConnection
) {
4640 AssertIsOnIOThread();
4642 QM_TRY_UNWRAP(auto storageVersion
,
4643 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection
, GetSchemaVersion
));
4645 // Hacky downgrade logic!
4646 // If we see major.minor of 3.0, downgrade it to be 2.1.
4647 if (storageVersion
== kHackyPreDowngradeStorageVersion
) {
4648 storageVersion
= kHackyPostDowngradeStorageVersion
;
4649 QM_TRY(MOZ_TO_RESULT(aConnection
.SetSchemaVersion(storageVersion
)),
4651 [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
4654 QM_TRY(OkIf(GetMajorStorageVersion(storageVersion
) <= kMajorStorageVersion
),
4655 NS_ERROR_FAILURE
, [](const auto&) {
4656 NS_WARNING("Unable to initialize storage, version is too high!");
4659 if (storageVersion
< kStorageVersion
) {
4660 const bool newDatabase
= !storageVersion
;
4662 QM_TRY_INSPECT(const auto& storageDir
, QM_NewLocalFile(*mStoragePath
));
4664 QM_TRY_INSPECT(const auto& storageDirExists
,
4665 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir
, Exists
));
4667 const bool newDirectory
= !storageDirExists
;
4670 // Set the page size first.
4671 if (kSQLitePageSizeOverride
) {
4672 QM_TRY(MOZ_TO_RESULT(aConnection
.ExecuteSimpleSQL(nsPrintfCString(
4673 "PRAGMA page_size = %" PRIu32
";", kSQLitePageSizeOverride
))));
4677 mozStorageTransaction
transaction(
4678 &aConnection
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
4680 QM_TRY(MOZ_TO_RESULT(transaction
.Start()));
4682 // An upgrade method can upgrade the database, the storage or both.
4683 // The upgrade loop below can only be avoided when there's no database and
4684 // no storage yet (e.g. new profile).
4685 if (newDatabase
&& newDirectory
) {
4686 QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection
)));
4691 const int32_t& storageVersion
,
4692 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection
, GetSchemaVersion
),
4693 QM_ASSERT_UNREACHABLE
);
4694 MOZ_ASSERT(storageVersion
== kStorageVersion
);
4698 QM_TRY(MOZ_TO_RESULT(aConnection
.ExecuteSimpleSQL(
4699 nsLiteralCString("INSERT INTO database (cache_version) "
4702 // This logic needs to change next time we change the storage!
4703 static_assert(kStorageVersion
== int32_t((2 << 16) + 3),
4704 "Upgrade function needed due to storage version increase.");
4706 while (storageVersion
!= kStorageVersion
) {
4707 if (storageVersion
== 0) {
4708 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection
)));
4709 } else if (storageVersion
== MakeStorageVersion(1, 0)) {
4710 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection
)));
4711 } else if (storageVersion
== MakeStorageVersion(2, 0)) {
4712 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection
)));
4713 } else if (storageVersion
== MakeStorageVersion(2, 1)) {
4714 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection
)));
4715 } else if (storageVersion
== MakeStorageVersion(2, 2)) {
4716 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection
)));
4718 QM_FAIL(NS_ERROR_FAILURE
, []() {
4720 "Unable to initialize storage, no upgrade path is "
4725 QM_TRY_UNWRAP(storageVersion
, MOZ_TO_RESULT_INVOKE_MEMBER(
4726 aConnection
, GetSchemaVersion
));
4729 MOZ_ASSERT(storageVersion
== kStorageVersion
);
4732 QM_TRY(MOZ_TO_RESULT(transaction
.Commit()));
4738 OkOrErr
QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
4739 AssertIsOnIOThread();
4742 const auto& lsArchiveTmpFile
,
4743 QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath
)));
4745 QM_TRY_INSPECT(const bool& exists
,
4746 QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile
, Exists
));
4749 QM_TRY(QM_TO_RESULT(lsArchiveTmpFile
->Remove(false)));
4755 Result
<Ok
, nsresult
> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
4756 nsIFile
& aLsArchiveFile
) {
4757 AssertIsOnIOThread();
4760 const bool& lsArchiveFileExisted
,
4761 ([this, &aLsArchiveFile
]() -> Result
<bool, nsresult
> {
4762 QM_TRY_INSPECT(const bool& exists
,
4763 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile
, Exists
));
4766 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile
));
4772 QM_TRY_UNWRAP(auto connection
,
4773 CreateLocalStorageArchiveConnection(aLsArchiveFile
));
4775 QM_TRY_INSPECT(const auto& initialized
,
4776 IsLocalStorageArchiveInitialized(*connection
));
4779 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection
)));
4782 QM_TRY_UNWRAP(int32_t version
, LoadLocalStorageArchiveVersion(*connection
));
4784 if (version
> kLocalStorageArchiveVersion
) {
4785 // Close local storage archive connection. We are going to remove underlying
4787 QM_TRY(MOZ_TO_RESULT(connection
->Close()));
4789 // This will wipe the archive and any migrated data and recopy the archive
4790 // from webappsstore.sqlite.
4791 QM_TRY_UNWRAP(connection
, DowngradeLocalStorageArchive(aLsArchiveFile
));
4793 QM_TRY_UNWRAP(version
, LoadLocalStorageArchiveVersion(*connection
));
4795 MOZ_ASSERT(version
== kLocalStorageArchiveVersion
);
4796 } else if (version
!= kLocalStorageArchiveVersion
) {
4797 // The version can be zero either when the archive didn't exist or it did
4798 // exist, but the archive was created without any version information.
4799 // We don't need to do any upgrades only if it didn't exist because existing
4800 // archives without version information must be recopied to really fix bug
4801 // 1542104. See also bug 1546305 which introduced archive versions.
4802 if (!lsArchiveFileExisted
) {
4803 MOZ_ASSERT(version
== 0);
4805 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(
4806 connection
, kLocalStorageArchiveVersion
)));
4808 static_assert(kLocalStorageArchiveVersion
== 4,
4809 "Upgrade function needed due to LocalStorage archive "
4810 "version increase.");
4812 while (version
!= kLocalStorageArchiveVersion
) {
4814 // Close local storage archive connection. We are going to remove
4816 QM_TRY(MOZ_TO_RESULT(connection
->Close()));
4818 // This won't do an "upgrade" in a normal sense. It will wipe the
4819 // archive and any migrated data and recopy the archive from
4820 // webappsstore.sqlite
4821 QM_TRY_UNWRAP(connection
, UpgradeLocalStorageArchiveFromLessThan4To4(
4823 } /* else if (version == 4) {
4824 QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection)));
4827 QM_FAIL(Err(NS_ERROR_FAILURE
), []() {
4829 "Unable to initialize LocalStorage archive, no upgrade path "
4834 QM_TRY_UNWRAP(version
, LoadLocalStorageArchiveVersion(*connection
));
4837 MOZ_ASSERT(version
== kLocalStorageArchiveVersion
);
4841 // At this point, we have finished initializing the local storage archive, and
4842 // can continue storage initialization. We don't know though if the actual
4843 // data in the archive file is readable. We can't do a PRAGMA integrity_check
4844 // here though, because that would be too heavyweight.
4849 Result
<Ok
, nsresult
> QuotaManager::CreateEmptyLocalStorageArchive(
4850 nsIFile
& aLsArchiveFile
) const {
4851 AssertIsOnIOThread();
4853 QM_TRY_INSPECT(const bool& exists
,
4854 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile
, Exists
));
4856 // If it exists, remove it. It might be a directory, so remove it recursively.
4858 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile
.Remove(true)));
4860 // XXX If we crash right here, the next session will copy the archive from
4861 // webappsstore.sqlite again!
4862 // XXX Create a marker file before removing the archive which can be
4863 // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
4864 // archive instead of recopying it from webapppstore.sqlite (in other
4865 // words, finishing what was started here).
4868 QM_TRY_INSPECT(const auto& ss
,
4869 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr
<mozIStorageService
>,
4870 MOZ_SELECT_OVERLOAD(do_GetService
),
4871 MOZ_STORAGE_SERVICE_CONTRACTID
));
4873 QM_TRY_UNWRAP(const auto connection
,
4874 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4875 nsCOMPtr
<mozIStorageConnection
>, ss
, OpenUnsharedDatabase
,
4876 &aLsArchiveFile
, mozIStorageService::CONNECTION_DEFAULT
));
4878 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection
)));
4880 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection
)));
4882 QM_TRY(MOZ_TO_RESULT(
4883 SaveLocalStorageArchiveVersion(connection
, kLocalStorageArchiveVersion
)));
4888 RefPtr
<BoolPromise
> QuotaManager::InitializeStorage() {
4889 AssertIsOnOwningThread();
4891 // If storage is initialized but there's a clear storage or shutdown storage
4892 // operation already scheduled, we can't immediately resolve the promise and
4893 // return from the function because the clear or shutdown storage operation
4894 // uninitializes storage.
4895 if (mStorageInitialized
&& !mShutdownStorageOpCount
) {
4896 return BoolPromise::CreateAndResolve(true, __func__
);
4899 RefPtr
<UniversalDirectoryLock
> directoryLock
= CreateDirectoryLockInternal(
4900 Nullable
<PersistenceType
>(), OriginScope::FromNull(),
4901 Nullable
<Client::Type
>(),
4902 /* aExclusive */ false);
4904 return directoryLock
->Acquire()->Then(
4905 GetCurrentSerialEventTarget(), __func__
,
4906 [self
= RefPtr(this),
4907 directoryLock
](const BoolPromise::ResolveOrRejectValue
& aValue
) mutable {
4908 if (aValue
.IsReject()) {
4909 return BoolPromise::CreateAndReject(aValue
.RejectValue(), __func__
);
4912 return self
->InitializeStorage(std::move(directoryLock
));
4916 RefPtr
<BoolPromise
> QuotaManager::InitializeStorage(
4917 RefPtr
<UniversalDirectoryLock
> aDirectoryLock
) {
4918 AssertIsOnOwningThread();
4919 MOZ_ASSERT(aDirectoryLock
);
4921 if (mStorageInitialized
&& !mShutdownStorageOpCount
) {
4922 return BoolPromise::CreateAndResolve(true, __func__
);
4925 auto initializeStorageOp
=
4926 CreateInitOp(WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock
));
4928 RegisterNormalOriginOp(*initializeStorageOp
);
4930 initializeStorageOp
->RunImmediately();
4932 return initializeStorageOp
->OnResults()->Then(
4933 GetCurrentSerialEventTarget(), __func__
,
4934 [self
= RefPtr(this)](const BoolPromise::ResolveOrRejectValue
& aValue
) {
4935 if (aValue
.IsReject()) {
4936 return BoolPromise::CreateAndReject(aValue
.RejectValue(), __func__
);
4939 self
->mStorageInitialized
= true;
4941 return BoolPromise::CreateAndResolve(true, __func__
);
4945 RefPtr
<BoolPromise
> QuotaManager::StorageInitialized() {
4946 AssertIsOnOwningThread();
4948 auto storageInitializedOp
=
4949 CreateStorageInitializedOp(WrapMovingNotNullUnchecked(this));
4951 RegisterNormalOriginOp(*storageInitializedOp
);
4953 storageInitializedOp
->RunImmediately();
4955 return storageInitializedOp
->OnResults();
4958 nsresult
QuotaManager::EnsureStorageIsInitializedInternal() {
4959 DiagnosticAssertIsOnIOThread();
4961 const auto innerFunc
=
4962 [&](const auto& firstInitializationAttempt
) -> nsresult
{
4963 if (mStorageConnection
) {
4964 MOZ_ASSERT(firstInitializationAttempt
.Recorded());
4968 QM_TRY_INSPECT(const auto& storageFile
, QM_NewLocalFile(mBasePath
));
4969 QM_TRY(MOZ_TO_RESULT(storageFile
->Append(mStorageName
+ kSQLiteSuffix
)));
4971 QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile
)));
4973 QM_TRY_INSPECT(const auto& ss
,
4974 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr
<mozIStorageService
>,
4975 MOZ_SELECT_OVERLOAD(do_GetService
),
4976 MOZ_STORAGE_SERVICE_CONTRACTID
));
4982 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4983 nsCOMPtr
<mozIStorageConnection
>, ss
, OpenUnsharedDatabase
,
4984 storageFile
, mozIStorageService::CONNECTION_DEFAULT
),
4986 IsDatabaseCorruptionError
,
4988 ErrToDefaultOk
<nsCOMPtr
<mozIStorageConnection
>>));
4991 // Nuke the database file.
4992 QM_TRY(MOZ_TO_RESULT(storageFile
->Remove(false)));
4994 QM_TRY_UNWRAP(connection
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4995 nsCOMPtr
<mozIStorageConnection
>, ss
,
4996 OpenUnsharedDatabase
, storageFile
,
4997 mozIStorageService::CONNECTION_DEFAULT
));
5000 // We want extra durability for this important file.
5001 QM_TRY(MOZ_TO_RESULT(
5002 connection
->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns
)));
5004 // Check to make sure that the storage version is correct.
5005 QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection
)));
5007 QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
5009 QM_TRY_INSPECT(const auto& lsArchiveFile
,
5010 GetLocalStorageArchiveFile(*mStoragePath
));
5012 if (CachedNextGenLocalStorageEnabled()) {
5013 QM_TRY(QM_OR_ELSE_WARN_IF(
5015 MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile
),
5017 IsDatabaseCorruptionError
,
5019 ([&](const nsresult rv
) -> Result
<Ok
, nsresult
> {
5020 QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile
));
5024 MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile
)));
5027 QM_TRY_UNWRAP(mCacheUsable
, MaybeCreateOrUpgradeCache(*connection
));
5029 if (mCacheUsable
&& gInvalidateQuotaCache
) {
5030 QM_TRY(InvalidateCache(*connection
));
5032 gInvalidateQuotaCache
= false;
5035 mStorageConnection
= std::move(connection
);
5040 return ExecuteInitialization(
5041 Initialization::Storage
,
5042 "dom::quota::FirstInitializationAttempt::Storage"_ns
, innerFunc
);
5045 RefPtr
<BoolPromise
> QuotaManager::TemporaryStorageInitialized() {
5046 AssertIsOnOwningThread();
5048 auto temporaryStorageInitializedOp
=
5049 CreateTemporaryStorageInitializedOp(WrapMovingNotNullUnchecked(this));
5051 RegisterNormalOriginOp(*temporaryStorageInitializedOp
);
5053 temporaryStorageInitializedOp
->RunImmediately();
5055 return temporaryStorageInitializedOp
->OnResults();
5058 RefPtr
<UniversalDirectoryLockPromise
> QuotaManager::OpenStorageDirectory(
5059 const Nullable
<PersistenceType
>& aPersistenceType
,
5060 const OriginScope
& aOriginScope
, const Nullable
<Client::Type
>& aClientType
,
5062 Maybe
<RefPtr
<UniversalDirectoryLock
>&> aPendingDirectoryLockOut
) {
5063 AssertIsOnOwningThread();
5065 RefPtr
<UniversalDirectoryLock
> storageDirectoryLock
;
5067 RefPtr
<BoolPromise
> storageDirectoryLockPromise
;
5069 if (mStorageInitialized
&& !mShutdownStorageOpCount
) {
5070 storageDirectoryLockPromise
= BoolPromise::CreateAndResolve(true, __func__
);
5072 storageDirectoryLock
= CreateDirectoryLockInternal(
5073 Nullable
<PersistenceType
>(), OriginScope::FromNull(),
5074 Nullable
<Client::Type
>(),
5075 /* aExclusive */ false);
5077 storageDirectoryLockPromise
= storageDirectoryLock
->Acquire();
5080 RefPtr
<UniversalDirectoryLock
> universalDirectoryLock
=
5081 CreateDirectoryLockInternal(aPersistenceType
, aOriginScope
, aClientType
,
5084 RefPtr
<BoolPromise
> universalDirectoryLockPromise
=
5085 universalDirectoryLock
->Acquire();
5087 if (aPendingDirectoryLockOut
.isSome()) {
5088 aPendingDirectoryLockOut
.ref() = universalDirectoryLock
;
5091 return storageDirectoryLockPromise
5092 ->Then(GetCurrentSerialEventTarget(), __func__
,
5093 [self
= RefPtr(this),
5094 storageDirectoryLock
= std::move(storageDirectoryLock
)](
5095 const BoolPromise::ResolveOrRejectValue
& aValue
) mutable {
5096 if (aValue
.IsReject()) {
5097 return BoolPromise::CreateAndReject(aValue
.RejectValue(),
5101 if (!storageDirectoryLock
) {
5102 return BoolPromise::CreateAndResolve(true, __func__
);
5105 return self
->InitializeStorage(std::move(storageDirectoryLock
));
5107 ->Then(GetCurrentSerialEventTarget(), __func__
,
5108 [universalDirectoryLockPromise
=
5109 std::move(universalDirectoryLockPromise
)](
5110 const BoolPromise::ResolveOrRejectValue
& aValue
) mutable {
5111 if (aValue
.IsReject()) {
5112 return BoolPromise::CreateAndReject(aValue
.RejectValue(),
5116 return std::move(universalDirectoryLockPromise
);
5118 ->Then(GetCurrentSerialEventTarget(), __func__
,
5119 [universalDirectoryLock
= std::move(universalDirectoryLock
)](
5120 const BoolPromise::ResolveOrRejectValue
& aValue
) mutable {
5121 if (aValue
.IsReject()) {
5122 return UniversalDirectoryLockPromise::CreateAndReject(
5123 aValue
.RejectValue(), __func__
);
5126 return UniversalDirectoryLockPromise::CreateAndResolve(
5127 std::move(universalDirectoryLock
), __func__
);
5131 RefPtr
<ClientDirectoryLockPromise
> QuotaManager::OpenClientDirectory(
5132 const ClientMetadata
& aClientMetadata
,
5133 Maybe
<RefPtr
<ClientDirectoryLock
>&> aPendingDirectoryLockOut
) {
5134 AssertIsOnOwningThread();
5136 nsTArray
<RefPtr
<BoolPromise
>> promises
;
5138 RefPtr
<UniversalDirectoryLock
> storageDirectoryLock
;
5140 if (!mStorageInitialized
|| mShutdownStorageOpCount
) {
5141 storageDirectoryLock
= CreateDirectoryLockInternal(
5142 Nullable
<PersistenceType
>(), OriginScope::FromNull(),
5143 Nullable
<Client::Type
>(),
5144 /* aExclusive */ false);
5145 promises
.AppendElement(storageDirectoryLock
->Acquire());
5148 RefPtr
<ClientDirectoryLock
> clientDirectoryLock
=
5149 CreateDirectoryLock(aClientMetadata
, /* aExclusive */ false);
5151 promises
.AppendElement(clientDirectoryLock
->Acquire());
5153 if (aPendingDirectoryLockOut
.isSome()) {
5154 aPendingDirectoryLockOut
.ref() = clientDirectoryLock
;
5157 return BoolPromise::All(GetCurrentSerialEventTarget(), promises
)
5159 GetCurrentSerialEventTarget(), __func__
,
5160 [self
= RefPtr(this),
5161 storageDirectoryLock
= std::move(storageDirectoryLock
)](
5162 const CopyableTArray
<bool>& aResolveValues
) mutable {
5163 if (!storageDirectoryLock
) {
5164 return BoolPromise::CreateAndResolve(true, __func__
);
5167 return self
->InitializeStorage(std::move(storageDirectoryLock
));
5169 [](nsresult aRejectValue
) {
5170 return BoolPromise::CreateAndReject(aRejectValue
, __func__
);
5172 ->Then(GetCurrentSerialEventTarget(), __func__
,
5173 [clientDirectoryLock
= std::move(clientDirectoryLock
)](
5174 const BoolPromise::ResolveOrRejectValue
& aValue
) mutable {
5175 if (aValue
.IsReject()) {
5176 return ClientDirectoryLockPromise::CreateAndReject(
5177 aValue
.RejectValue(), __func__
);
5179 return ClientDirectoryLockPromise::CreateAndResolve(
5180 std::move(clientDirectoryLock
), __func__
);
5184 RefPtr
<ClientDirectoryLock
> QuotaManager::CreateDirectoryLock(
5185 const ClientMetadata
& aClientMetadata
, bool aExclusive
) {
5186 AssertIsOnOwningThread();
5188 return DirectoryLockImpl::Create(
5189 WrapNotNullUnchecked(this), aClientMetadata
.mPersistenceType
,
5190 aClientMetadata
, aClientMetadata
.mClientType
, aExclusive
);
5193 RefPtr
<UniversalDirectoryLock
> QuotaManager::CreateDirectoryLockInternal(
5194 const Nullable
<PersistenceType
>& aPersistenceType
,
5195 const OriginScope
& aOriginScope
, const Nullable
<Client::Type
>& aClientType
,
5197 AssertIsOnOwningThread();
5199 return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
5200 aPersistenceType
, aOriginScope
,
5201 aClientType
, aExclusive
);
5204 Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
>
5205 QuotaManager::EnsurePersistentOriginIsInitialized(
5206 const OriginMetadata
& aOriginMetadata
) {
5207 AssertIsOnIOThread();
5208 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
);
5209 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection
);
5211 const auto innerFunc
= [&aOriginMetadata
,
5212 this](const auto& firstInitializationAttempt
)
5213 -> mozilla::Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
> {
5214 QM_TRY_UNWRAP(auto directory
, GetOriginDirectory(aOriginMetadata
));
5216 if (mInitializedOrigins
.Contains(aOriginMetadata
.mOrigin
)) {
5217 MOZ_ASSERT(firstInitializationAttempt
.Recorded());
5218 return std::pair(std::move(directory
), false);
5221 QM_TRY_INSPECT(const bool& created
, EnsureOriginDirectory(*directory
));
5224 const int64_t& timestamp
,
5225 ([this, created
, &directory
,
5226 &aOriginMetadata
]() -> Result
<int64_t, nsresult
> {
5228 const int64_t timestamp
= PR_Now();
5230 // Only creating .metadata-v2 to reduce IO.
5231 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory
, timestamp
,
5232 /* aPersisted */ true,
5238 // Get the metadata. We only use the timestamp.
5239 QM_TRY_INSPECT(const auto& metadata
,
5240 LoadFullOriginMetadataWithRestore(directory
));
5242 MOZ_ASSERT(metadata
.mLastAccessTime
<= PR_Now());
5244 return metadata
.mLastAccessTime
;
5247 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT
,
5248 aOriginMetadata
, timestamp
,
5249 /* aPersisted */ true, directory
)));
5251 mInitializedOrigins
.AppendElement(aOriginMetadata
.mOrigin
);
5253 return std::pair(std::move(directory
), created
);
5256 return ExecuteOriginInitialization(
5257 aOriginMetadata
.mOrigin
, OriginInitialization::PersistentOrigin
,
5258 "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns
,
5262 bool QuotaManager::IsTemporaryOriginInitialized(
5263 const OriginMetadata
& aOriginMetadata
) const {
5264 AssertIsOnIOThread();
5265 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
5267 MutexAutoLock
lock(mQuotaMutex
);
5269 RefPtr
<OriginInfo
> originInfo
=
5270 LockedGetOriginInfo(aOriginMetadata
.mPersistenceType
, aOriginMetadata
);
5272 return static_cast<bool>(originInfo
);
5275 Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
>
5276 QuotaManager::EnsureTemporaryOriginIsInitialized(
5277 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
) {
5278 AssertIsOnIOThread();
5279 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
5280 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
== aPersistenceType
);
5281 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection
);
5282 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal
);
5284 const auto innerFunc
= [&aOriginMetadata
, this](const auto&)
5285 -> mozilla::Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
> {
5286 // Get directory for this origin and persistence type.
5287 QM_TRY_UNWRAP(auto directory
, GetOriginDirectory(aOriginMetadata
));
5289 QM_TRY_INSPECT(const bool& created
, EnsureOriginDirectory(*directory
));
5292 const int64_t timestamp
=
5293 NoteOriginDirectoryCreated(aOriginMetadata
, /* aPersisted */ false);
5295 // Only creating .metadata-v2 to reduce IO.
5296 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory
, timestamp
,
5297 /* aPersisted */ false,
5301 // TODO: If the metadata file exists and we didn't call
5302 // LoadFullOriginMetadataWithRestore for it (because the quota info
5303 // was loaded from the cache), then the group in the metadata file
5304 // may be wrong, so it should be checked and eventually updated.
5305 // It's not a big deal that we are not doing it here, because the
5306 // origin will be marked as "accessed", so
5307 // LoadFullOriginMetadataWithRestore will be called for the metadata
5308 // file in next session in LoadQuotaFromCache.
5310 return std::pair(std::move(directory
), created
);
5313 return ExecuteOriginInitialization(
5314 aOriginMetadata
.mOrigin
, OriginInitialization::TemporaryOrigin
,
5315 "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns
,
5319 RefPtr
<BoolPromise
> QuotaManager::InitializePersistentClient(
5320 const PrincipalInfo
& aPrincipalInfo
, Client::Type aClientType
) {
5321 AssertIsOnOwningThread();
5323 auto initializePersistentClientOp
= CreateInitializePersistentClientOp(
5324 WrapMovingNotNullUnchecked(this), aPrincipalInfo
, aClientType
);
5326 RegisterNormalOriginOp(*initializePersistentClientOp
);
5328 initializePersistentClientOp
->RunImmediately();
5330 return initializePersistentClientOp
->OnResults();
5333 Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
>
5334 QuotaManager::EnsurePersistentClientIsInitialized(
5335 const ClientMetadata
& aClientMetadata
) {
5336 AssertIsOnIOThread();
5337 MOZ_ASSERT(aClientMetadata
.mPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
);
5338 MOZ_ASSERT(Client::IsValidType(aClientMetadata
.mClientType
));
5339 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
5340 MOZ_DIAGNOSTIC_ASSERT(IsOriginInitialized(aClientMetadata
.mOrigin
));
5342 QM_TRY_UNWRAP(auto directory
, GetOriginDirectory(aClientMetadata
));
5344 QM_TRY(MOZ_TO_RESULT(
5345 directory
->Append(Client::TypeToString(aClientMetadata
.mClientType
))));
5347 QM_TRY_UNWRAP(bool created
, EnsureDirectory(*directory
));
5349 return std::pair(std::move(directory
), created
);
5352 RefPtr
<BoolPromise
> QuotaManager::InitializeTemporaryClient(
5353 PersistenceType aPersistenceType
, const PrincipalInfo
& aPrincipalInfo
,
5354 Client::Type aClientType
) {
5355 AssertIsOnOwningThread();
5357 auto initializeTemporaryClientOp
= CreateInitializeTemporaryClientOp(
5358 WrapMovingNotNullUnchecked(this), aPersistenceType
, aPrincipalInfo
,
5361 RegisterNormalOriginOp(*initializeTemporaryClientOp
);
5363 initializeTemporaryClientOp
->RunImmediately();
5365 return initializeTemporaryClientOp
->OnResults();
5368 Result
<std::pair
<nsCOMPtr
<nsIFile
>, bool>, nsresult
>
5369 QuotaManager::EnsureTemporaryClientIsInitialized(
5370 const ClientMetadata
& aClientMetadata
) {
5371 AssertIsOnIOThread();
5372 MOZ_ASSERT(aClientMetadata
.mPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
5373 MOZ_ASSERT(Client::IsValidType(aClientMetadata
.mClientType
));
5374 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
5375 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
5376 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryOriginInitialized(aClientMetadata
));
5378 QM_TRY_UNWRAP(auto directory
, GetOriginDirectory(aClientMetadata
));
5380 QM_TRY(MOZ_TO_RESULT(
5381 directory
->Append(Client::TypeToString(aClientMetadata
.mClientType
))));
5383 QM_TRY_UNWRAP(bool created
, EnsureDirectory(*directory
));
5385 return std::pair(std::move(directory
), created
);
5388 RefPtr
<BoolPromise
> QuotaManager::InitializeTemporaryStorage() {
5389 AssertIsOnOwningThread();
5391 // If temporary storage is initialized but there's a clear storage or
5392 // shutdown storage operation already scheduled, we can't immediately resolve
5393 // the promise and return from the function because the clear or shutdown
5394 // storage operation uninitializes storage.
5395 if (mTemporaryStorageInitialized
&& !mShutdownStorageOpCount
) {
5396 return BoolPromise::CreateAndResolve(true, __func__
);
5399 RefPtr
<UniversalDirectoryLock
> directoryLock
= CreateDirectoryLockInternal(
5400 Nullable
<PersistenceType
>(), OriginScope::FromNull(),
5401 Nullable
<Client::Type
>(),
5402 /* aExclusive */ false);
5404 return directoryLock
->Acquire()->Then(
5405 GetCurrentSerialEventTarget(), __func__
,
5406 [self
= RefPtr(this),
5407 directoryLock
](const BoolPromise::ResolveOrRejectValue
& aValue
) mutable {
5408 if (aValue
.IsReject()) {
5409 return BoolPromise::CreateAndReject(aValue
.RejectValue(), __func__
);
5412 return self
->InitializeTemporaryStorage(std::move(directoryLock
));
5416 RefPtr
<BoolPromise
> QuotaManager::InitializeTemporaryStorage(
5417 RefPtr
<UniversalDirectoryLock
> aDirectoryLock
) {
5418 AssertIsOnOwningThread();
5419 MOZ_ASSERT(aDirectoryLock
);
5421 if (mTemporaryStorageInitialized
&& !mShutdownStorageOpCount
) {
5422 return BoolPromise::CreateAndResolve(true, __func__
);
5425 auto initializeTemporaryStorageOp
= CreateInitTemporaryStorageOp(
5426 WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock
));
5428 RegisterNormalOriginOp(*initializeTemporaryStorageOp
);
5430 initializeTemporaryStorageOp
->RunImmediately();
5432 return initializeTemporaryStorageOp
->OnResults()->Then(
5433 GetCurrentSerialEventTarget(), __func__
,
5434 [self
= RefPtr(this)](const BoolPromise::ResolveOrRejectValue
& aValue
) {
5435 if (aValue
.IsReject()) {
5436 return BoolPromise::CreateAndReject(aValue
.RejectValue(), __func__
);
5439 self
->mTemporaryStorageInitialized
= true;
5441 return BoolPromise::CreateAndResolve(true, __func__
);
5445 nsresult
QuotaManager::EnsureTemporaryStorageIsInitializedInternal() {
5446 AssertIsOnIOThread();
5447 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection
);
5449 const auto innerFunc
=
5450 [&](const auto& firstInitializationAttempt
) -> nsresult
{
5451 if (mTemporaryStorageInitializedInternal
) {
5452 MOZ_ASSERT(firstInitializationAttempt
.Recorded());
5457 const auto& storageDir
,
5458 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr
<nsIFile
>,
5459 MOZ_SELECT_OVERLOAD(do_CreateInstance
),
5460 NS_LOCAL_FILE_CONTRACTID
));
5462 QM_TRY(MOZ_TO_RESULT(storageDir
->InitWithPath(GetStoragePath())));
5464 // The storage directory must exist before calling GetTemporaryStorageLimit.
5465 QM_TRY_INSPECT(const bool& created
, EnsureDirectory(*storageDir
));
5469 QM_TRY_UNWRAP(mTemporaryStorageLimit
,
5470 GetTemporaryStorageLimit(*storageDir
));
5472 QM_TRY(MOZ_TO_RESULT(LoadQuota()));
5474 mTemporaryStorageInitializedInternal
= true;
5476 CleanupTemporaryStorage();
5479 QM_TRY(InvalidateCache(*mStorageConnection
));
5485 return ExecuteInitialization(
5486 Initialization::TemporaryStorage
,
5487 "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns
, innerFunc
);
5490 RefPtr
<BoolPromise
> QuotaManager::ClearStoragesForOrigin(
5491 const Maybe
<PersistenceType
>& aPersistenceType
,
5492 const PrincipalInfo
& aPrincipalInfo
,
5493 const Maybe
<Client::Type
>& aClientType
) {
5494 AssertIsOnOwningThread();
5496 auto clearOriginOp
=
5497 CreateClearOriginOp(WrapMovingNotNullUnchecked(this), aPersistenceType
,
5498 aPrincipalInfo
, aClientType
);
5500 RegisterNormalOriginOp(*clearOriginOp
);
5502 clearOriginOp
->RunImmediately();
5504 return clearOriginOp
->OnResults();
5507 RefPtr
<BoolPromise
> QuotaManager::ClearStoragesForOriginPrefix(
5508 const Maybe
<PersistenceType
>& aPersistenceType
,
5509 const PrincipalInfo
& aPrincipalInfo
) {
5510 AssertIsOnOwningThread();
5512 auto clearStoragesForOriginPrefixOp
= CreateClearStoragesForOriginPrefixOp(
5513 WrapMovingNotNullUnchecked(this), aPersistenceType
, aPrincipalInfo
);
5515 RegisterNormalOriginOp(*clearStoragesForOriginPrefixOp
);
5517 clearStoragesForOriginPrefixOp
->RunImmediately();
5519 return clearStoragesForOriginPrefixOp
->OnResults();
5522 RefPtr
<BoolPromise
> QuotaManager::ClearStoragesForOriginAttributesPattern(
5523 const OriginAttributesPattern
& aPattern
) {
5524 AssertIsOnOwningThread();
5527 CreateClearDataOp(WrapMovingNotNullUnchecked(this), aPattern
);
5529 RegisterNormalOriginOp(*clearDataOp
);
5531 clearDataOp
->RunImmediately();
5533 return clearDataOp
->OnResults();
5536 RefPtr
<BoolPromise
> QuotaManager::ClearPrivateRepository() {
5537 AssertIsOnOwningThread();
5539 auto clearPrivateRepositoryOp
=
5540 CreateClearPrivateRepositoryOp(WrapMovingNotNullUnchecked(this));
5542 RegisterNormalOriginOp(*clearPrivateRepositoryOp
);
5544 clearPrivateRepositoryOp
->RunImmediately();
5546 return clearPrivateRepositoryOp
->OnResults();
5549 RefPtr
<BoolPromise
> QuotaManager::ClearStorage() {
5550 AssertIsOnOwningThread();
5552 auto clearStorageOp
= CreateClearStorageOp(WrapMovingNotNullUnchecked(this));
5554 RegisterNormalOriginOp(*clearStorageOp
);
5556 clearStorageOp
->RunImmediately();
5558 // Storage clearing also shuts it down, so we need to increses the counter
5560 mShutdownStorageOpCount
++;
5562 return clearStorageOp
->OnResults()->Then(
5563 GetCurrentSerialEventTarget(), __func__
,
5564 [self
= RefPtr(this)](const BoolPromise::ResolveOrRejectValue
& aValue
) {
5565 self
->mShutdownStorageOpCount
--;
5567 if (aValue
.IsReject()) {
5568 return BoolPromise::CreateAndReject(aValue
.RejectValue(), __func__
);
5571 self
->mTemporaryStorageInitialized
= false;
5572 self
->mStorageInitialized
= false;
5574 return BoolPromise::CreateAndResolve(true, __func__
);
5578 RefPtr
<BoolPromise
> QuotaManager::ShutdownStorage() {
5579 AssertIsOnOwningThread();
5581 auto shutdownStorageOp
=
5582 CreateShutdownStorageOp(WrapMovingNotNullUnchecked(this));
5584 RegisterNormalOriginOp(*shutdownStorageOp
);
5586 shutdownStorageOp
->RunImmediately();
5588 mShutdownStorageOpCount
++;
5590 return shutdownStorageOp
->OnResults()->Then(
5591 GetCurrentSerialEventTarget(), __func__
,
5592 [self
= RefPtr(this)](const BoolPromise::ResolveOrRejectValue
& aValue
) {
5593 self
->mShutdownStorageOpCount
--;
5595 if (aValue
.IsReject()) {
5596 return BoolPromise::CreateAndReject(aValue
.RejectValue(), __func__
);
5599 self
->mTemporaryStorageInitialized
= false;
5600 self
->mStorageInitialized
= false;
5602 return BoolPromise::CreateAndResolve(true, __func__
);
5606 void QuotaManager::ShutdownStorageInternal() {
5607 AssertIsOnIOThread();
5609 if (mStorageConnection
) {
5610 mInitializationInfo
.ResetOriginInitializationInfos();
5611 mInitializedOrigins
.Clear();
5613 if (mTemporaryStorageInitializedInternal
) {
5620 mTemporaryStorageInitializedInternal
= false;
5623 ReleaseIOThreadObjects();
5625 mStorageConnection
= nullptr;
5626 mCacheUsable
= false;
5629 mInitializationInfo
.ResetFirstInitializationAttempts();
5632 Result
<bool, nsresult
> QuotaManager::EnsureOriginDirectory(
5633 nsIFile
& aDirectory
) {
5634 AssertIsOnIOThread();
5636 QM_TRY_INSPECT(const bool& exists
,
5637 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory
, Exists
));
5641 const auto& leafName
,
5642 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString
, aDirectory
, GetLeafName
)
5643 .map([](const auto& leafName
) {
5644 return NS_ConvertUTF16toUTF8(leafName
);
5647 QM_TRY(OkIf(IsSanitizedOriginValid(leafName
)), Err(NS_ERROR_FAILURE
),
5650 "Preventing creation of a new origin directory which is not "
5651 "supported by our origin parser or is obsolete!");
5655 QM_TRY_RETURN(EnsureDirectory(aDirectory
));
5658 nsresult
QuotaManager::AboutToClearOrigins(
5659 const Nullable
<PersistenceType
>& aPersistenceType
,
5660 const OriginScope
& aOriginScope
,
5661 const Nullable
<Client::Type
>& aClientType
) {
5662 AssertIsOnIOThread();
5664 if (aClientType
.IsNull()) {
5665 for (Client::Type type
: AllClientTypes()) {
5666 QM_TRY(MOZ_TO_RESULT((*mClients
)[type
]->AboutToClearOrigins(
5667 aPersistenceType
, aOriginScope
)));
5670 QM_TRY(MOZ_TO_RESULT((*mClients
)[aClientType
.Value()]->AboutToClearOrigins(
5671 aPersistenceType
, aOriginScope
)));
5677 void QuotaManager::OriginClearCompleted(
5678 PersistenceType aPersistenceType
, const nsACString
& aOrigin
,
5679 const Nullable
<Client::Type
>& aClientType
) {
5680 AssertIsOnIOThread();
5682 if (aClientType
.IsNull()) {
5683 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
5684 mInitializedOrigins
.RemoveElement(aOrigin
);
5687 for (Client::Type type
: AllClientTypes()) {
5688 (*mClients
)[type
]->OnOriginClearCompleted(aPersistenceType
, aOrigin
);
5691 (*mClients
)[aClientType
.Value()]->OnOriginClearCompleted(aPersistenceType
,
5696 void QuotaManager::RepositoryClearCompleted(PersistenceType aPersistenceType
) {
5697 AssertIsOnIOThread();
5699 if (aPersistenceType
== PERSISTENCE_TYPE_PERSISTENT
) {
5700 mInitializedOrigins
.Clear();
5703 for (Client::Type type
: AllClientTypes()) {
5704 (*mClients
)[type
]->OnRepositoryClearCompleted(aPersistenceType
);
5708 Client
* QuotaManager::GetClient(Client::Type aClientType
) {
5709 MOZ_ASSERT(aClientType
>= Client::IDB
);
5710 MOZ_ASSERT(aClientType
< Client::TypeMax());
5712 return (*mClients
)[aClientType
];
5715 const AutoTArray
<Client::Type
, Client::TYPE_MAX
>&
5716 QuotaManager::AllClientTypes() {
5717 if (CachedNextGenLocalStorageEnabled()) {
5718 return *mAllClientTypes
;
5720 return *mAllClientTypesExceptLS
;
5723 uint64_t QuotaManager::GetGroupLimit() const {
5724 // To avoid one group evicting all the rest, limit the amount any one group
5725 // can use to 20% resp. a fifth. To prevent individual sites from using
5726 // exorbitant amounts of storage where there is a lot of free space, cap the
5727 // group limit to 10GB.
5728 const auto x
= std::min
<uint64_t>(mTemporaryStorageLimit
/ 5, 10 GB
);
5730 // In low-storage situations, make an exception (while not exceeding the total
5732 return std::min
<uint64_t>(mTemporaryStorageLimit
,
5733 std::max
<uint64_t>(x
, 10 MB
));
5736 std::pair
<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate(
5737 const OriginMetadata
& aOriginMetadata
) {
5738 AssertIsOnIOThread();
5740 uint64_t totalGroupUsage
= 0;
5743 MutexAutoLock
lock(mQuotaMutex
);
5745 GroupInfoPair
* pair
;
5746 if (mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
5747 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
5748 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(type
);
5750 if (type
== PERSISTENCE_TYPE_DEFAULT
) {
5751 RefPtr
<OriginInfo
> originInfo
=
5752 groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
5754 if (originInfo
&& originInfo
->LockedPersisted()) {
5755 return std::pair(mTemporaryStorageUsage
, mTemporaryStorageLimit
);
5759 AssertNoOverflow(totalGroupUsage
, groupInfo
->mUsage
);
5760 totalGroupUsage
+= groupInfo
->mUsage
;
5766 return std::pair(totalGroupUsage
, GetGroupLimit());
5769 uint64_t QuotaManager::GetOriginUsage(
5770 const PrincipalMetadata
& aPrincipalMetadata
) {
5771 AssertIsOnIOThread();
5776 MutexAutoLock
lock(mQuotaMutex
);
5778 GroupInfoPair
* pair
;
5779 if (mGroupInfoPairs
.Get(aPrincipalMetadata
.mGroup
, &pair
)) {
5780 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
5781 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(type
);
5783 RefPtr
<OriginInfo
> originInfo
=
5784 groupInfo
->LockedGetOriginInfo(aPrincipalMetadata
.mOrigin
);
5786 AssertNoOverflow(usage
, originInfo
->LockedUsage());
5787 usage
+= originInfo
->LockedUsage();
5797 Maybe
<FullOriginMetadata
> QuotaManager::GetFullOriginMetadata(
5798 const OriginMetadata
& aOriginMetadata
) {
5799 AssertIsOnIOThread();
5800 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection
);
5801 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal
);
5803 MutexAutoLock
lock(mQuotaMutex
);
5805 RefPtr
<OriginInfo
> originInfo
=
5806 LockedGetOriginInfo(aOriginMetadata
.mPersistenceType
, aOriginMetadata
);
5808 return Some(originInfo
->LockedFlattenToFullOriginMetadata());
5814 void QuotaManager::NotifyStoragePressure(uint64_t aUsage
) {
5815 mQuotaMutex
.AssertNotCurrentThreadOwns();
5817 RefPtr
<StoragePressureRunnable
> storagePressureRunnable
=
5818 new StoragePressureRunnable(aUsage
);
5820 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable
));
5824 void QuotaManager::GetStorageId(PersistenceType aPersistenceType
,
5825 const nsACString
& aOrigin
,
5826 Client::Type aClientType
,
5827 nsACString
& aDatabaseId
) {
5829 str
.AppendInt(aPersistenceType
);
5831 str
.Append(aOrigin
);
5833 str
.AppendInt(aClientType
);
5839 bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo
& aPrincipalInfo
) {
5840 switch (aPrincipalInfo
.type()) {
5841 // A system principal is acceptable.
5842 case PrincipalInfo::TSystemPrincipalInfo
: {
5846 // Validate content principals to ensure that the spec, originNoSuffix and
5847 // baseDomain are sane.
5848 case PrincipalInfo::TContentPrincipalInfo
: {
5849 const ContentPrincipalInfo
& info
=
5850 aPrincipalInfo
.get_ContentPrincipalInfo();
5852 // Verify the principal spec parses.
5853 nsCOMPtr
<nsIURI
> uri
;
5854 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri
), info
.spec())), false);
5856 nsCOMPtr
<nsIPrincipal
> principal
=
5857 BasePrincipal::CreateContentPrincipal(uri
, info
.attrs());
5858 QM_TRY(MOZ_TO_RESULT(principal
), false);
5860 // Verify the principal originNoSuffix matches spec.
5861 QM_TRY_INSPECT(const auto& originNoSuffix
,
5862 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString
, principal
,
5866 if (NS_WARN_IF(originNoSuffix
!= info
.originNoSuffix())) {
5867 QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
5868 originNoSuffix
.get(), info
.originNoSuffix().get());
5872 if (NS_WARN_IF(info
.originNoSuffix().EqualsLiteral(kChromeOrigin
))) {
5876 if (NS_WARN_IF(info
.originNoSuffix().FindChar('^', 0) != -1)) {
5877 QM_WARNING("originNoSuffix (%s) contains the '^' character!",
5878 info
.originNoSuffix().get());
5882 // Verify the principal baseDomain exists.
5883 if (NS_WARN_IF(info
.baseDomain().IsVoid())) {
5887 // Verify the principal baseDomain matches spec.
5888 QM_TRY_INSPECT(const auto& baseDomain
,
5889 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString
, principal
,
5893 if (NS_WARN_IF(baseDomain
!= info
.baseDomain())) {
5894 QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
5895 baseDomain
.get(), info
.baseDomain().get());
5907 // Null and expanded principals are not acceptable.
5911 Result
<PrincipalMetadata
, nsresult
>
5912 QuotaManager::GetInfoFromValidatedPrincipalInfo(
5913 const PrincipalInfo
& aPrincipalInfo
) {
5914 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo
));
5916 switch (aPrincipalInfo
.type()) {
5917 case PrincipalInfo::TSystemPrincipalInfo
: {
5918 return GetInfoForChrome();
5921 case PrincipalInfo::TContentPrincipalInfo
: {
5922 const ContentPrincipalInfo
& info
=
5923 aPrincipalInfo
.get_ContentPrincipalInfo();
5926 info
.attrs().CreateSuffix(suffix
);
5928 nsCString origin
= info
.originNoSuffix() + suffix
;
5930 if (IsUUIDOrigin(origin
)) {
5931 QM_TRY_INSPECT(const auto& originalOrigin
,
5932 GetOriginFromStorageOrigin(origin
));
5934 nsCOMPtr
<nsIPrincipal
> principal
=
5935 BasePrincipal::CreateContentPrincipal(originalOrigin
);
5936 QM_TRY(MOZ_TO_RESULT(principal
));
5938 PrincipalInfo principalInfo
;
5940 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal
, &principalInfo
)));
5942 return GetInfoFromValidatedPrincipalInfo(principalInfo
);
5945 PrincipalMetadata principalMetadata
;
5947 principalMetadata
.mSuffix
= suffix
;
5949 principalMetadata
.mGroup
= info
.baseDomain() + suffix
;
5951 principalMetadata
.mOrigin
= origin
;
5953 if (info
.attrs().mPrivateBrowsingId
!= 0) {
5954 QM_TRY_UNWRAP(principalMetadata
.mStorageOrigin
,
5955 EnsureStorageOriginFromOrigin(origin
));
5957 principalMetadata
.mStorageOrigin
= origin
;
5960 principalMetadata
.mIsPrivate
= info
.attrs().mPrivateBrowsingId
!= 0;
5962 return principalMetadata
;
5966 MOZ_ASSERT_UNREACHABLE("Should never get here!");
5967 return Err(NS_ERROR_UNEXPECTED
);
5973 nsAutoCString
QuotaManager::GetOriginFromValidatedPrincipalInfo(
5974 const PrincipalInfo
& aPrincipalInfo
) {
5975 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo
));
5977 switch (aPrincipalInfo
.type()) {
5978 case PrincipalInfo::TSystemPrincipalInfo
: {
5979 return nsAutoCString
{GetOriginForChrome()};
5982 case PrincipalInfo::TContentPrincipalInfo
: {
5983 const ContentPrincipalInfo
& info
=
5984 aPrincipalInfo
.get_ContentPrincipalInfo();
5986 nsAutoCString suffix
;
5988 info
.attrs().CreateSuffix(suffix
);
5990 return info
.originNoSuffix() + suffix
;
5994 MOZ_CRASH("Should never get here!");
6000 Result
<PrincipalMetadata
, nsresult
> QuotaManager::GetInfoFromPrincipal(
6001 nsIPrincipal
* aPrincipal
) {
6002 MOZ_ASSERT(aPrincipal
);
6004 if (aPrincipal
->IsSystemPrincipal()) {
6005 return GetInfoForChrome();
6008 if (aPrincipal
->GetIsNullPrincipal()) {
6009 NS_WARNING("IndexedDB not supported from this principal!");
6010 return Err(NS_ERROR_FAILURE
);
6013 PrincipalMetadata principalMetadata
;
6015 QM_TRY(MOZ_TO_RESULT(aPrincipal
->GetOrigin(principalMetadata
.mOrigin
)));
6017 if (principalMetadata
.mOrigin
.EqualsLiteral(kChromeOrigin
)) {
6018 NS_WARNING("Non-chrome principal can't use chrome origin!");
6019 return Err(NS_ERROR_FAILURE
);
6022 aPrincipal
->OriginAttributesRef().CreateSuffix(principalMetadata
.mSuffix
);
6024 nsAutoCString baseDomain
;
6025 QM_TRY(MOZ_TO_RESULT(aPrincipal
->GetBaseDomain(baseDomain
)));
6027 MOZ_ASSERT(!baseDomain
.IsEmpty());
6029 principalMetadata
.mGroup
= baseDomain
+ principalMetadata
.mSuffix
;
6031 principalMetadata
.mStorageOrigin
= principalMetadata
.mOrigin
;
6033 principalMetadata
.mIsPrivate
= aPrincipal
->GetPrivateBrowsingId() != 0;
6035 return principalMetadata
;
6038 Result
<PrincipalMetadata
, nsresult
> QuotaManager::GetInfoFromWindow(
6039 nsPIDOMWindowOuter
* aWindow
) {
6040 MOZ_ASSERT(NS_IsMainThread());
6041 MOZ_ASSERT(aWindow
);
6043 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aWindow
);
6044 QM_TRY(OkIf(sop
), Err(NS_ERROR_FAILURE
));
6046 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
6047 QM_TRY(OkIf(principal
), Err(NS_ERROR_FAILURE
));
6049 return GetInfoFromPrincipal(principal
);
6053 Result
<nsAutoCString
, nsresult
> QuotaManager::GetOriginFromPrincipal(
6054 nsIPrincipal
* aPrincipal
) {
6055 MOZ_ASSERT(NS_IsMainThread());
6056 MOZ_ASSERT(aPrincipal
);
6058 if (aPrincipal
->IsSystemPrincipal()) {
6059 return nsAutoCString
{GetOriginForChrome()};
6062 if (aPrincipal
->GetIsNullPrincipal()) {
6063 NS_WARNING("IndexedDB not supported from this principal!");
6064 return Err(NS_ERROR_FAILURE
);
6067 QM_TRY_UNWRAP(const auto origin
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6068 nsAutoCString
, aPrincipal
, GetOrigin
));
6070 if (origin
.EqualsLiteral(kChromeOrigin
)) {
6071 NS_WARNING("Non-chrome principal can't use chrome origin!");
6072 return Err(NS_ERROR_FAILURE
);
6079 Result
<nsAutoCString
, nsresult
> QuotaManager::GetOriginFromWindow(
6080 nsPIDOMWindowOuter
* aWindow
) {
6081 MOZ_ASSERT(NS_IsMainThread());
6082 MOZ_ASSERT(aWindow
);
6084 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aWindow
);
6085 QM_TRY(OkIf(sop
), Err(NS_ERROR_FAILURE
));
6087 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
6088 QM_TRY(OkIf(principal
), Err(NS_ERROR_FAILURE
));
6090 QM_TRY_RETURN(GetOriginFromPrincipal(principal
));
6094 PrincipalMetadata
QuotaManager::GetInfoForChrome() {
6096 GetOriginForChrome(),
6097 GetOriginForChrome(),
6098 GetOriginForChrome(),
6103 nsLiteralCString
QuotaManager::GetOriginForChrome() {
6104 return nsLiteralCString
{kChromeOrigin
};
6108 bool QuotaManager::IsOriginInternal(const nsACString
& aOrigin
) {
6109 MOZ_ASSERT(!aOrigin
.IsEmpty());
6111 // The first prompt is not required for these origins.
6112 if (aOrigin
.EqualsLiteral(kChromeOrigin
) ||
6113 StringBeginsWith(aOrigin
, nsDependentCString(kAboutHomeOriginPrefix
)) ||
6114 StringBeginsWith(aOrigin
, nsDependentCString(kIndexedDBOriginPrefix
)) ||
6115 StringBeginsWith(aOrigin
, nsDependentCString(kResourceOriginPrefix
))) {
6123 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString
& aOrigin1
,
6124 const nsACString
& aOrigin2
) {
6125 return MakeSanitizedOriginCString(aOrigin1
) ==
6126 MakeSanitizedOriginCString(aOrigin2
);
6130 Result
<PrincipalInfo
, nsresult
> QuotaManager::ParseOrigin(
6131 const nsACString
& aOrigin
) {
6132 // An origin string either corresponds to a SystemPrincipalInfo or a
6133 // ContentPrincipalInfo, see
6134 // QuotaManager::GetOriginFromValidatedPrincipalInfo.
6137 OriginAttributes attrs
;
6138 nsCString originalSuffix
;
6139 const OriginParser::ResultType result
= OriginParser::ParseOrigin(
6140 MakeSanitizedOriginCString(aOrigin
), spec
, &attrs
, originalSuffix
);
6141 QM_TRY(MOZ_TO_RESULT(result
== OriginParser::ValidOrigin
));
6144 const auto& principal
,
6145 ([&spec
, &attrs
]() -> Result
<nsCOMPtr
<nsIPrincipal
>, nsresult
> {
6146 if (spec
.EqualsLiteral(kChromeOrigin
)) {
6147 return nsCOMPtr
<nsIPrincipal
>(SystemPrincipal::Get());
6150 nsCOMPtr
<nsIURI
> uri
;
6151 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri
), spec
)));
6153 return nsCOMPtr
<nsIPrincipal
>(
6154 BasePrincipal::CreateContentPrincipal(uri
, attrs
));
6156 QM_TRY(MOZ_TO_RESULT(principal
));
6158 PrincipalInfo principalInfo
;
6159 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal
, &principalInfo
)));
6161 return std::move(principalInfo
);
6165 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache
= true; }
6167 uint64_t QuotaManager::LockedCollectOriginsForEviction(
6168 uint64_t aMinSizeToBeFreed
, nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
) {
6169 mQuotaMutex
.AssertCurrentThreadOwns();
6171 RefPtr
<CollectOriginsHelper
> helper
=
6172 new CollectOriginsHelper(mQuotaMutex
, aMinSizeToBeFreed
);
6174 // Unlock while calling out to XPCOM (code behind the dispatch method needs
6175 // to acquire its own lock which can potentially lead to a deadlock and it
6176 // also calls an observer that can do various stuff like IO, so it's better
6177 // to not hold our mutex while that happens).
6179 MutexAutoUnlock
autoUnlock(mQuotaMutex
);
6181 MOZ_ALWAYS_SUCCEEDS(mOwningThread
->Dispatch(helper
, NS_DISPATCH_NORMAL
));
6184 return helper
->BlockAndReturnOriginsForEviction(aLocks
);
6187 void QuotaManager::LockedRemoveQuotaForRepository(
6188 PersistenceType aPersistenceType
) {
6189 mQuotaMutex
.AssertCurrentThreadOwns();
6190 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6192 for (auto iter
= mGroupInfoPairs
.Iter(); !iter
.Done(); iter
.Next()) {
6193 auto& pair
= iter
.Data();
6195 if (RefPtr
<GroupInfo
> groupInfo
=
6196 pair
->LockedGetGroupInfo(aPersistenceType
)) {
6197 groupInfo
->LockedRemoveOriginInfos();
6199 pair
->LockedClearGroupInfo(aPersistenceType
);
6201 if (!pair
->LockedHasGroupInfos()) {
6208 void QuotaManager::LockedRemoveQuotaForOrigin(
6209 const OriginMetadata
& aOriginMetadata
) {
6210 mQuotaMutex
.AssertCurrentThreadOwns();
6211 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6213 GroupInfoPair
* pair
;
6214 if (!mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
6220 if (RefPtr
<GroupInfo
> groupInfo
=
6221 pair
->LockedGetGroupInfo(aOriginMetadata
.mPersistenceType
)) {
6222 groupInfo
->LockedRemoveOriginInfo(aOriginMetadata
.mOrigin
);
6224 if (!groupInfo
->LockedHasOriginInfos()) {
6225 pair
->LockedClearGroupInfo(aOriginMetadata
.mPersistenceType
);
6227 if (!pair
->LockedHasGroupInfos()) {
6228 mGroupInfoPairs
.Remove(aOriginMetadata
.mGroup
);
6234 already_AddRefed
<GroupInfo
> QuotaManager::LockedGetOrCreateGroupInfo(
6235 PersistenceType aPersistenceType
, const nsACString
& aSuffix
,
6236 const nsACString
& aGroup
) {
6237 mQuotaMutex
.AssertCurrentThreadOwns();
6238 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6240 GroupInfoPair
* const pair
=
6241 mGroupInfoPairs
.GetOrInsertNew(aGroup
, aSuffix
, aGroup
);
6243 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
6245 groupInfo
= new GroupInfo(pair
, aPersistenceType
);
6246 pair
->LockedSetGroupInfo(aPersistenceType
, groupInfo
);
6249 return groupInfo
.forget();
6252 already_AddRefed
<OriginInfo
> QuotaManager::LockedGetOriginInfo(
6253 PersistenceType aPersistenceType
,
6254 const OriginMetadata
& aOriginMetadata
) const {
6255 mQuotaMutex
.AssertCurrentThreadOwns();
6256 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_PERSISTENT
);
6258 GroupInfoPair
* pair
;
6259 if (mGroupInfoPairs
.Get(aOriginMetadata
.mGroup
, &pair
)) {
6260 RefPtr
<GroupInfo
> groupInfo
= pair
->LockedGetGroupInfo(aPersistenceType
);
6262 return groupInfo
->LockedGetOriginInfo(aOriginMetadata
.mOrigin
);
6269 template <typename Iterator
>
6270 void QuotaManager::MaybeInsertNonPersistedOriginInfos(
6271 Iterator aDest
, const RefPtr
<GroupInfo
>& aTemporaryGroupInfo
,
6272 const RefPtr
<GroupInfo
>& aDefaultGroupInfo
,
6273 const RefPtr
<GroupInfo
>& aPrivateGroupInfo
) {
6274 const auto copy
= [&aDest
](const GroupInfo
& groupInfo
) {
6276 groupInfo
.mOriginInfos
.cbegin(), groupInfo
.mOriginInfos
.cend(), aDest
,
6277 [](const auto& originInfo
) { return !originInfo
->LockedPersisted(); });
6280 if (aTemporaryGroupInfo
) {
6281 MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY
==
6282 aTemporaryGroupInfo
->GetPersistenceType());
6284 copy(*aTemporaryGroupInfo
);
6286 if (aDefaultGroupInfo
) {
6287 MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT
==
6288 aDefaultGroupInfo
->GetPersistenceType());
6290 copy(*aDefaultGroupInfo
);
6292 if (aPrivateGroupInfo
) {
6293 MOZ_ASSERT(PERSISTENCE_TYPE_PRIVATE
==
6294 aPrivateGroupInfo
->GetPersistenceType());
6295 copy(*aPrivateGroupInfo
);
6299 template <typename Collect
, typename Pred
>
6300 QuotaManager::OriginInfosFlatTraversable
6301 QuotaManager::CollectLRUOriginInfosUntil(Collect
&& aCollect
, Pred
&& aPred
) {
6302 OriginInfosFlatTraversable originInfos
;
6304 std::forward
<Collect
>(aCollect
)(MakeBackInserter(originInfos
));
6306 originInfos
.Sort(OriginInfoAccessTimeComparator());
6308 const auto foundIt
= std::find_if(originInfos
.cbegin(), originInfos
.cend(),
6309 std::forward
<Pred
>(aPred
));
6311 originInfos
.TruncateLength(foundIt
- originInfos
.cbegin());
6316 QuotaManager::OriginInfosNestedTraversable
6317 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
6318 MutexAutoLock
lock(mQuotaMutex
);
6320 OriginInfosNestedTraversable originInfos
;
6322 for (const auto& entry
: mGroupInfoPairs
) {
6323 const auto& pair
= entry
.GetData();
6325 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
6328 uint64_t groupUsage
= 0;
6330 const RefPtr
<GroupInfo
> temporaryGroupInfo
=
6331 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
);
6332 if (temporaryGroupInfo
) {
6333 groupUsage
+= temporaryGroupInfo
->mUsage
;
6336 const RefPtr
<GroupInfo
> defaultGroupInfo
=
6337 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
);
6338 if (defaultGroupInfo
) {
6339 groupUsage
+= defaultGroupInfo
->mUsage
;
6342 const RefPtr
<GroupInfo
> privateGroupInfo
=
6343 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE
);
6344 if (privateGroupInfo
) {
6345 groupUsage
+= privateGroupInfo
->mUsage
;
6348 if (groupUsage
> 0) {
6349 QuotaManager
* quotaManager
= QuotaManager::Get();
6350 MOZ_ASSERT(quotaManager
, "Shouldn't be null!");
6352 if (groupUsage
> quotaManager
->GetGroupLimit()) {
6353 originInfos
.AppendElement(CollectLRUOriginInfosUntil(
6354 [&temporaryGroupInfo
, &defaultGroupInfo
,
6355 &privateGroupInfo
](auto inserter
) {
6356 MaybeInsertNonPersistedOriginInfos(
6357 std::move(inserter
), temporaryGroupInfo
, defaultGroupInfo
,
6360 [&groupUsage
, quotaManager
](const auto& originInfo
) {
6361 groupUsage
-= originInfo
->LockedUsage();
6363 return groupUsage
<= quotaManager
->GetGroupLimit();
6372 QuotaManager::OriginInfosNestedTraversable
6373 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
6374 MutexAutoLock
lock(mQuotaMutex
);
6376 QuotaManager::OriginInfosNestedTraversable res
;
6377 res
.AppendElement(CollectLRUOriginInfosUntil(
6378 // XXX The lambda only needs to capture this, but due to Bug 1421435 it
6380 [&](auto inserter
) {
6381 for (const auto& entry
: mGroupInfoPairs
) {
6382 const auto& pair
= entry
.GetData();
6384 MOZ_ASSERT(!entry
.GetKey().IsEmpty());
6387 MaybeInsertNonPersistedOriginInfos(
6388 inserter
, pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY
),
6389 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT
),
6390 pair
->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE
));
6393 [temporaryStorageUsage
= mTemporaryStorageUsage
,
6394 temporaryStorageLimit
= mTemporaryStorageLimit
,
6395 doomedUsage
= uint64_t{0}](const auto& originInfo
) mutable {
6396 if (temporaryStorageUsage
- doomedUsage
<= temporaryStorageLimit
) {
6400 doomedUsage
+= originInfo
->LockedUsage();
6407 void QuotaManager::ClearOrigins(
6408 const OriginInfosNestedTraversable
& aDoomedOriginInfos
) {
6409 AssertIsOnIOThread();
6411 // If we are in shutdown, we could break off early from clearing origins.
6412 // In such cases, we would like to track the ones that were already cleared
6413 // up, such that other essential cleanup could be performed on clearedOrigins.
6414 // clearedOrigins is used in calls to LockedRemoveQuotaForOrigin and
6415 // OriginClearCompleted below. We could have used a collection of OriginInfos
6416 // rather than flattening them to OriginMetadata but groupInfo in OriginInfo
6417 // is just a raw ptr and LockedRemoveQuotaForOrigin might delete groupInfo and
6418 // as a result, we would not be able to get origin persistence type required
6419 // in OriginClearCompleted call after lockedRemoveQuotaForOrigin call.
6420 nsTArray
<OriginMetadata
> clearedOrigins
;
6422 // XXX Does this need to be done a) in order and/or b) sequentially?
6423 for (const auto& doomedOriginInfo
:
6424 Flatten
<OriginInfosFlatTraversable::value_type
>(aDoomedOriginInfos
)) {
6427 MutexAutoLock
lock(mQuotaMutex
);
6428 MOZ_ASSERT(!doomedOriginInfo
->LockedPersisted());
6432 // TODO: We are currently only checking for this flag here which
6433 // means that we cannot break off once we start cleaning an origin. It
6434 // could be better if we could check for shutdown flag while cleaning an
6435 // origin such that we could break off early from the cleaning process if
6436 // we are stuck cleaning on one huge origin. Bug1797098 has been filed to
6438 if (QuotaManager::IsShuttingDown()) {
6442 auto originMetadata
= doomedOriginInfo
->FlattenToOriginMetadata();
6444 DeleteOriginDirectory(originMetadata
);
6446 clearedOrigins
.AppendElement(std::move(originMetadata
));
6450 MutexAutoLock
lock(mQuotaMutex
);
6452 for (const auto& clearedOrigin
: clearedOrigins
) {
6453 LockedRemoveQuotaForOrigin(clearedOrigin
);
6457 for (const auto& clearedOrigin
: clearedOrigins
) {
6458 OriginClearCompleted(clearedOrigin
.mPersistenceType
, clearedOrigin
.mOrigin
,
6459 Nullable
<Client::Type
>());
6463 void QuotaManager::CleanupTemporaryStorage() {
6464 AssertIsOnIOThread();
6466 // Evicting origins that exceed their group limit also affects the global
6467 // temporary storage usage, so these steps have to be taken sequentially.
6468 // Combining them doesn't seem worth the added complexity.
6469 ClearOrigins(GetOriginInfosExceedingGroupLimit());
6470 ClearOrigins(GetOriginInfosExceedingGlobalLimit());
6472 if (mTemporaryStorageUsage
> mTemporaryStorageLimit
) {
6473 // If disk space is still low after origin clear, notify storage pressure.
6474 NotifyStoragePressure(mTemporaryStorageUsage
);
6478 void QuotaManager::DeleteOriginDirectory(
6479 const OriginMetadata
& aOriginMetadata
) {
6480 QM_TRY_INSPECT(const auto& directory
, GetOriginDirectory(aOriginMetadata
),
6483 nsresult rv
= directory
->Remove(true);
6484 if (rv
!= NS_ERROR_FILE_NOT_FOUND
&& NS_FAILED(rv
)) {
6485 // This should never fail if we've closed all storage connections
6487 NS_ERROR("Failed to remove directory!");
6491 void QuotaManager::FinalizeOriginEviction(
6492 nsTArray
<RefPtr
<OriginDirectoryLock
>>&& aLocks
) {
6493 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
6495 auto finalizeOriginEviction
= [locks
= std::move(aLocks
)]() mutable {
6496 QuotaManager
* quotaManager
= QuotaManager::Get();
6497 MOZ_ASSERT(quotaManager
);
6499 RefPtr
<OriginOperationBase
> op
= CreateFinalizeOriginEvictionOp(
6500 WrapMovingNotNull(quotaManager
), std::move(locks
));
6502 op
->RunImmediately();
6505 if (IsOnBackgroundThread()) {
6506 finalizeOriginEviction();
6508 MOZ_ALWAYS_SUCCEEDS(mOwningThread
->Dispatch(
6509 NS_NewRunnableFunction(
6510 "dom::quota::QuotaManager::FinalizeOriginEviction",
6511 std::move(finalizeOriginEviction
)),
6512 NS_DISPATCH_NORMAL
));
6516 Result
<Ok
, nsresult
> QuotaManager::ArchiveOrigins(
6517 const nsTArray
<FullOriginMetadata
>& aFullOriginMetadatas
) {
6518 AssertIsOnIOThread();
6519 MOZ_ASSERT(!aFullOriginMetadatas
.IsEmpty());
6521 QM_TRY_INSPECT(const auto& storageArchivesDir
,
6522 QM_NewLocalFile(*mStorageArchivesPath
));
6524 // Create another subdir, so once we decide to remove all temporary archives,
6525 // we can remove only the subdir and the parent directory can still be used
6526 // for something else or similar in future. Otherwise, we would have to
6527 // figure out a new name for it.
6528 QM_TRY(MOZ_TO_RESULT(storageArchivesDir
->Append(u
"0"_ns
)));
6531 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters
, &now
);
6533 const auto dateStr
=
6534 nsPrintfCString("%04hd-%02" PRId32
"-%02" PRId32
, now
.tm_year
,
6535 now
.tm_month
+ 1, now
.tm_mday
);
6538 const auto& storageArchiveDir
,
6539 CloneFileAndAppend(*storageArchivesDir
, NS_ConvertASCIItoUTF16(dateStr
)));
6541 QM_TRY(MOZ_TO_RESULT(
6542 storageArchiveDir
->CreateUnique(nsIFile::DIRECTORY_TYPE
, 0700)));
6544 QM_TRY_INSPECT(const auto& defaultStorageArchiveDir
,
6545 CloneFileAndAppend(*storageArchiveDir
,
6546 nsLiteralString(DEFAULT_DIRECTORY_NAME
)));
6548 QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir
,
6549 CloneFileAndAppend(*storageArchiveDir
,
6550 nsLiteralString(TEMPORARY_DIRECTORY_NAME
)));
6552 for (const auto& fullOriginMetadata
: aFullOriginMetadatas
) {
6554 IsBestEffortPersistenceType(fullOriginMetadata
.mPersistenceType
));
6556 QM_TRY_INSPECT(const auto& directory
,
6557 GetOriginDirectory(fullOriginMetadata
));
6559 // The origin could have been removed, for example due to corruption.
6565 directory
->MoveTo(fullOriginMetadata
.mPersistenceType
==
6566 PERSISTENCE_TYPE_DEFAULT
6567 ? defaultStorageArchiveDir
6568 : temporaryStorageArchiveDir
,
6570 .map([](Ok
) { return true; }),
6572 ([](const nsresult rv
) { return rv
== NS_ERROR_FILE_NOT_FOUND
; }),
6577 RemoveQuotaForOrigin(fullOriginMetadata
.mPersistenceType
,
6578 fullOriginMetadata
);
6585 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType
)
6586 -> DirectoryLockTable
& {
6587 switch (aPersistenceType
) {
6588 case PERSISTENCE_TYPE_TEMPORARY
:
6589 return mTemporaryDirectoryLockTable
;
6590 case PERSISTENCE_TYPE_DEFAULT
:
6591 return mDefaultDirectoryLockTable
;
6592 case PERSISTENCE_TYPE_PRIVATE
:
6593 return mPrivateDirectoryLockTable
;
6595 case PERSISTENCE_TYPE_PERSISTENT
:
6596 case PERSISTENCE_TYPE_INVALID
:
6598 MOZ_CRASH("Bad persistence type value!");
6602 void QuotaManager::ClearDirectoryLockTables() {
6603 AssertIsOnOwningThread();
6605 for (const PersistenceType type
: kBestEffortPersistenceTypes
) {
6606 DirectoryLockTable
& directoryLockTable
= GetDirectoryLockTable(type
);
6608 if (!IsShuttingDown()) {
6609 for (const auto& entry
: directoryLockTable
) {
6610 const auto& array
= entry
.GetData();
6612 // It doesn't matter which lock is used, they all have the same
6613 // persistence type and origin metadata.
6614 MOZ_ASSERT(!array
->IsEmpty());
6615 const auto& lock
= array
->ElementAt(0);
6617 UpdateOriginAccessTime(lock
->GetPersistenceType(),
6618 lock
->OriginMetadata());
6622 directoryLockTable
.Clear();
6626 bool QuotaManager::IsSanitizedOriginValid(const nsACString
& aSanitizedOrigin
) {
6627 AssertIsOnIOThread();
6629 // Do not parse this sanitized origin string, if we already parsed it.
6630 return mValidOrigins
.LookupOrInsertWith(
6631 aSanitizedOrigin
, [&aSanitizedOrigin
] {
6633 OriginAttributes attrs
;
6634 nsCString originalSuffix
;
6635 const auto result
= OriginParser::ParseOrigin(aSanitizedOrigin
, spec
,
6636 &attrs
, originalSuffix
);
6638 return result
== OriginParser::ValidOrigin
;
6642 Result
<nsCString
, nsresult
> QuotaManager::EnsureStorageOriginFromOrigin(
6643 const nsACString
& aOrigin
) {
6644 MutexAutoLock
lock(mQuotaMutex
);
6648 mOriginToStorageOriginMap
.TryLookupOrInsertWith(
6649 aOrigin
, [this, &aOrigin
]() -> Result
<nsCString
, nsresult
> {
6650 OriginAttributes originAttributes
;
6652 nsCString originNoSuffix
;
6653 QM_TRY(MOZ_TO_RESULT(
6654 originAttributes
.PopulateFromOrigin(aOrigin
, originNoSuffix
)));
6656 nsCOMPtr
<nsIURI
> uri
;
6657 QM_TRY(MOZ_TO_RESULT(
6658 NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID
)
6659 .SetSpec(originNoSuffix
)
6660 .SetScheme(kUUIDOriginScheme
)
6661 .SetHost(NSID_TrimBracketsASCII(nsID::GenerateUUID()))
6665 nsCOMPtr
<nsIPrincipal
> principal
=
6666 BasePrincipal::CreateContentPrincipal(uri
, OriginAttributes
{});
6667 QM_TRY(MOZ_TO_RESULT(principal
));
6669 QM_TRY_UNWRAP(auto origin
,
6670 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6671 nsAutoCString
, principal
, GetOrigin
));
6673 mStorageOriginToOriginMap
.WithEntryHandle(
6675 [&aOrigin
](auto entryHandle
) { entryHandle
.Insert(aOrigin
); });
6677 return nsCString(std::move(origin
));
6680 return nsCString(std::move(storageOrigin
));
6683 Result
<nsCString
, nsresult
> QuotaManager::GetOriginFromStorageOrigin(
6684 const nsACString
& aStorageOrigin
) {
6685 MutexAutoLock
lock(mQuotaMutex
);
6687 auto maybeOrigin
= mStorageOriginToOriginMap
.MaybeGet(aStorageOrigin
);
6688 if (maybeOrigin
.isNothing()) {
6689 return Err(NS_ERROR_FAILURE
);
6692 return maybeOrigin
.ref();
6695 int64_t QuotaManager::GenerateDirectoryLockId() {
6696 const int64_t directorylockId
= mNextDirectoryLockId
;
6698 if (CheckedInt64 result
= CheckedInt64(mNextDirectoryLockId
) + 1;
6700 mNextDirectoryLockId
= result
.value();
6702 NS_WARNING("Quota manager has run out of ids for directory locks!");
6704 // There's very little chance for this to happen given the max size of
6705 // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
6706 // to zero since such old directory locks shouldn't exist anymore.
6707 mNextDirectoryLockId
= 0;
6710 // TODO: Maybe add an assertion here to check that there is no existing
6711 // directory lock with given id.
6713 return directorylockId
;
6716 template <typename Func
>
6717 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization
,
6719 -> std::invoke_result_t
<Func
, const FirstInitializationAttempt
<
6720 Initialization
, StringGenerator
>&> {
6721 return quota::ExecuteInitialization(mInitializationInfo
, aInitialization
,
6722 std::forward
<Func
>(aFunc
));
6725 template <typename Func
>
6726 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization
,
6727 const nsACString
& aContext
,
6729 -> std::invoke_result_t
<Func
, const FirstInitializationAttempt
<
6730 Initialization
, StringGenerator
>&> {
6731 return quota::ExecuteInitialization(mInitializationInfo
, aInitialization
,
6732 aContext
, std::forward
<Func
>(aFunc
));
6735 template <typename Func
>
6736 auto QuotaManager::ExecuteOriginInitialization(
6737 const nsACString
& aOrigin
, const OriginInitialization aInitialization
,
6738 const nsACString
& aContext
, Func
&& aFunc
)
6739 -> std::invoke_result_t
<Func
, const FirstInitializationAttempt
<
6740 Initialization
, StringGenerator
>&> {
6741 return quota::ExecuteInitialization(
6742 mInitializationInfo
.MutableOriginInitializationInfoRef(
6743 aOrigin
, CreateIfNonExistent
{}),
6744 aInitialization
, aContext
, std::forward
<Func
>(aFunc
));
6747 /*******************************************************************************
6748 * Local class implementations
6749 ******************************************************************************/
6751 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex
& aMutex
,
6752 uint64_t aMinSizeToBeFreed
)
6753 : Runnable("dom::quota::CollectOriginsHelper"),
6754 mMinSizeToBeFreed(aMinSizeToBeFreed
),
6756 mCondVar(aMutex
, "CollectOriginsHelper::mCondVar"),
6759 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
6760 mMutex
.AssertCurrentThreadOwns();
6763 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
6764 nsTArray
<RefPtr
<OriginDirectoryLock
>>& aLocks
) {
6765 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
6766 mMutex
.AssertCurrentThreadOwns();
6772 mLocks
.SwapElements(aLocks
);
6773 return mSizeToBeFreed
;
6777 CollectOriginsHelper::Run() {
6778 AssertIsOnBackgroundThread();
6780 QuotaManager
* quotaManager
= QuotaManager::Get();
6781 NS_ASSERTION(quotaManager
, "Shouldn't be null!");
6783 // We use extra stack vars here to avoid race detector warnings (the same
6784 // memory accessed with and without the lock held).
6785 nsTArray
<RefPtr
<OriginDirectoryLock
>> locks
;
6786 uint64_t sizeToBeFreed
=
6787 quotaManager
->CollectOriginsForEviction(mMinSizeToBeFreed
, locks
);
6789 MutexAutoLock
lock(mMutex
);
6791 NS_ASSERTION(mWaiting
, "Huh?!");
6793 mLocks
.SwapElements(locks
);
6794 mSizeToBeFreed
= sizeToBeFreed
;
6802 StoragePressureRunnable::Run() {
6803 MOZ_ASSERT(NS_IsMainThread());
6805 nsCOMPtr
<nsIObserverService
> obsSvc
= mozilla::services::GetObserverService();
6806 if (NS_WARN_IF(!obsSvc
)) {
6807 return NS_ERROR_FAILURE
;
6810 nsCOMPtr
<nsISupportsPRUint64
> wrapper
=
6811 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID
);
6812 if (NS_WARN_IF(!wrapper
)) {
6813 return NS_ERROR_FAILURE
;
6816 wrapper
->SetData(mUsage
);
6818 obsSvc
->NotifyObservers(wrapper
, "QuotaManager::StoragePressure", u
"");
6823 TimeStamp
RecordTimeDeltaHelper::Start() {
6824 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
6826 // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
6827 // gLastOSWake is set, then this time duration would still be recorded with
6828 // key "Normal". We are assumming this is rather rare to happen.
6829 mStartTime
.init(TimeStamp::Now());
6830 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
6835 TimeStamp
RecordTimeDeltaHelper::End() {
6836 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
6838 mEndTime
.init(TimeStamp::Now());
6839 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
6845 RecordTimeDeltaHelper::Run() {
6846 MOZ_ASSERT(NS_IsMainThread());
6848 if (mInitializedTime
.isSome()) {
6849 // Keys for QM_QUOTA_INFO_LOAD_TIME_V0 and QM_SHUTDOWN_TIME_V0:
6850 // Normal: Normal conditions.
6851 // WasSuspended: There was a OS sleep so that it was suspended.
6852 // TimeStampErr1: The recorded start time is unexpectedly greater than the
6854 // TimeStampErr2: The initialized time for the recording class is unexpectly
6855 // greater than the last OS wake time.
6856 const auto key
= [this, wasSuspended
= gLastOSWake
> *mInitializedTime
]() {
6858 return "WasSuspended"_ns
;
6861 // XXX File a bug if we have data for this key.
6862 // We found negative values in our query in STMO for
6863 // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
6864 // because the documentation for TimeStamp::Now() says it returns a
6865 // monotonically increasing number.
6866 if (*mStartTime
> *mEndTime
) {
6867 return "TimeStampErr1"_ns
;
6870 if (*mInitializedTime
> gLastOSWake
) {
6871 return "TimeStampErr2"_ns
;
6877 Telemetry::AccumulateTimeDelta(mHistogram
, key
, *mStartTime
, *mEndTime
);
6882 gLastOSWake
= TimeStamp::Now();
6883 mInitializedTime
.init(gLastOSWake
);
6888 nsresult
StorageOperationBase::GetDirectoryMetadata(nsIFile
* aDirectory
,
6889 int64_t& aTimestamp
,
6891 nsACString
& aOrigin
,
6892 Nullable
<bool>& aIsApp
) {
6893 AssertIsOnIOThread();
6894 MOZ_ASSERT(aDirectory
);
6897 const auto& binaryStream
,
6898 GetBinaryInputStream(*aDirectory
, nsLiteralString(METADATA_FILE_NAME
)));
6900 QM_TRY_INSPECT(const uint64_t& timestamp
,
6901 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, Read64
));
6903 QM_TRY_INSPECT(const auto& group
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6904 nsCString
, binaryStream
, ReadCString
));
6906 QM_TRY_INSPECT(const auto& origin
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6907 nsCString
, binaryStream
, ReadCString
));
6909 Nullable
<bool> isApp
;
6911 if (NS_SUCCEEDED(binaryStream
->ReadBoolean(&value
))) {
6912 isApp
.SetValue(value
);
6915 aTimestamp
= timestamp
;
6918 aIsApp
= std::move(isApp
);
6922 nsresult
StorageOperationBase::GetDirectoryMetadata2(
6923 nsIFile
* aDirectory
, int64_t& aTimestamp
, nsACString
& aSuffix
,
6924 nsACString
& aGroup
, nsACString
& aOrigin
, bool& aIsApp
) {
6925 AssertIsOnIOThread();
6926 MOZ_ASSERT(aDirectory
);
6928 QM_TRY_INSPECT(const auto& binaryStream
,
6929 GetBinaryInputStream(*aDirectory
,
6930 nsLiteralString(METADATA_V2_FILE_NAME
)));
6932 QM_TRY_INSPECT(const uint64_t& timestamp
,
6933 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, Read64
));
6935 QM_TRY_INSPECT(const bool& persisted
,
6936 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, ReadBoolean
));
6937 Unused
<< persisted
;
6939 QM_TRY_INSPECT(const bool& reservedData1
,
6940 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, Read32
));
6941 Unused
<< reservedData1
;
6943 QM_TRY_INSPECT(const bool& reservedData2
,
6944 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, Read32
));
6945 Unused
<< reservedData2
;
6947 QM_TRY_INSPECT(const auto& suffix
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6948 nsCString
, binaryStream
, ReadCString
));
6950 QM_TRY_INSPECT(const auto& group
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6951 nsCString
, binaryStream
, ReadCString
));
6953 QM_TRY_INSPECT(const auto& origin
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6954 nsCString
, binaryStream
, ReadCString
));
6956 QM_TRY_INSPECT(const bool& isApp
,
6957 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream
, ReadBoolean
));
6959 aTimestamp
= timestamp
;
6967 int64_t StorageOperationBase::GetOriginLastModifiedTime(
6968 const OriginProps
& aOriginProps
) {
6969 return GetLastModifiedTime(*aOriginProps
.mPersistenceType
,
6970 *aOriginProps
.mDirectory
);
6973 nsresult
StorageOperationBase::RemoveObsoleteOrigin(
6974 const OriginProps
& aOriginProps
) {
6975 AssertIsOnIOThread();
6978 "Deleting obsolete %s directory that is no longer a legal "
6980 NS_ConvertUTF16toUTF8(aOriginProps
.mLeafName
).get());
6982 QM_TRY(MOZ_TO_RESULT(aOriginProps
.mDirectory
->Remove(/* recursive */ true)));
6987 Result
<bool, nsresult
> StorageOperationBase::MaybeRenameOrigin(
6988 const OriginProps
& aOriginProps
) {
6989 AssertIsOnIOThread();
6991 const nsAString
& oldLeafName
= aOriginProps
.mLeafName
;
6993 const auto newLeafName
=
6994 MakeSanitizedOriginString(aOriginProps
.mOriginMetadata
.mOrigin
);
6996 if (oldLeafName
== newLeafName
) {
7000 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7001 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7002 aOriginProps
.mOriginMetadata
)));
7004 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7005 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7006 /* aPersisted */ false, aOriginProps
.mOriginMetadata
)));
7008 QM_TRY_INSPECT(const auto& newFile
,
7009 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7010 nsCOMPtr
<nsIFile
>, *aOriginProps
.mDirectory
, GetParent
));
7012 QM_TRY(MOZ_TO_RESULT(newFile
->Append(newLeafName
)));
7014 QM_TRY_INSPECT(const bool& exists
,
7015 MOZ_TO_RESULT_INVOKE_MEMBER(newFile
, Exists
));
7019 "Can't rename %s directory to %s, the target already exists, removing "
7020 "instead of renaming!",
7021 NS_ConvertUTF16toUTF8(oldLeafName
).get(),
7022 NS_ConvertUTF16toUTF8(newLeafName
).get());
7025 QM_TRY(CallWithDelayedRetriesIfAccessDenied(
7026 [&exists
, &aOriginProps
, &newLeafName
] {
7028 QM_TRY_RETURN(MOZ_TO_RESULT(
7029 aOriginProps
.mDirectory
->Remove(/* recursive */ true)));
7031 QM_TRY_RETURN(MOZ_TO_RESULT(
7032 aOriginProps
.mDirectory
->RenameTo(nullptr, newLeafName
)));
7034 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
7035 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
7040 nsresult
StorageOperationBase::ProcessOriginDirectories() {
7041 AssertIsOnIOThread();
7042 MOZ_ASSERT(!mOriginProps
.IsEmpty());
7044 QuotaManager
* quotaManager
= QuotaManager::Get();
7045 MOZ_ASSERT(quotaManager
);
7047 for (auto& originProps
: mOriginProps
) {
7048 switch (originProps
.mType
) {
7049 case OriginProps::eChrome
: {
7050 originProps
.mOriginMetadata
= {QuotaManager::GetInfoForChrome(),
7051 *originProps
.mPersistenceType
};
7055 case OriginProps::eContent
: {
7056 nsCOMPtr
<nsIURI
> uri
;
7058 MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri
), originProps
.mSpec
)));
7060 nsCOMPtr
<nsIPrincipal
> principal
=
7061 BasePrincipal::CreateContentPrincipal(uri
, originProps
.mAttrs
);
7062 QM_TRY(MOZ_TO_RESULT(principal
));
7064 PrincipalInfo principalInfo
;
7066 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal
, &principalInfo
)));
7068 QM_WARNONLY_TRY_UNWRAP(
7070 MOZ_TO_RESULT(quotaManager
->IsPrincipalInfoValid(principalInfo
)));
7073 // Unknown directories during upgrade are allowed. Just warn if we
7075 UNKNOWN_FILE_WARNING(originProps
.mLeafName
);
7076 originProps
.mIgnore
= true;
7081 auto principalMetadata
,
7082 quotaManager
->GetInfoFromValidatedPrincipalInfo(principalInfo
));
7084 originProps
.mOriginMetadata
= {std::move(principalMetadata
),
7085 *originProps
.mPersistenceType
};
7090 case OriginProps::eObsolete
: {
7091 // There's no way to get info for obsolete origins.
7096 MOZ_CRASH("Bad type!");
7100 // Don't try to upgrade obsolete origins, remove them right after we detect
7102 for (const auto& originProps
: mOriginProps
) {
7103 if (originProps
.mType
== OriginProps::eObsolete
) {
7104 MOZ_ASSERT(originProps
.mOriginMetadata
.mSuffix
.IsEmpty());
7105 MOZ_ASSERT(originProps
.mOriginMetadata
.mGroup
.IsEmpty());
7106 MOZ_ASSERT(originProps
.mOriginMetadata
.mOrigin
.IsEmpty());
7108 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps
)));
7109 } else if (!originProps
.mIgnore
) {
7110 MOZ_ASSERT(!originProps
.mOriginMetadata
.mGroup
.IsEmpty());
7111 MOZ_ASSERT(!originProps
.mOriginMetadata
.mOrigin
.IsEmpty());
7113 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps
)));
7120 // XXX Do the fallible initialization in a separate non-static member function
7121 // of StorageOperationBase and eventually get rid of this method and use a
7122 // normal constructor instead.
7123 template <typename PersistenceTypeFunc
>
7124 nsresult
StorageOperationBase::OriginProps::Init(
7125 PersistenceTypeFunc
&& aPersistenceTypeFunc
) {
7126 AssertIsOnIOThread();
7128 QM_TRY_INSPECT(const auto& leafName
,
7129 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString
, *mDirectory
,
7132 // XXX Consider using QuotaManager::ParseOrigin here.
7134 OriginAttributes attrs
;
7135 nsCString originalSuffix
;
7136 OriginParser::ResultType result
= OriginParser::ParseOrigin(
7137 NS_ConvertUTF16toUTF8(leafName
), spec
, &attrs
, originalSuffix
);
7138 if (NS_WARN_IF(result
== OriginParser::InvalidOrigin
)) {
7139 mType
= OriginProps::eInvalid
;
7143 const auto persistenceType
= [&]() -> PersistenceType
{
7144 // XXX We shouldn't continue with initialization if OriginParser returned
7145 // anything else but ValidOrigin. Otherwise, we have to deal with empty
7146 // spec when the origin is obsolete, like here. The caller should handle
7147 // the errors. Until it's fixed, we have to treat obsolete origins as
7148 // origins with unknown/invalid persistence type.
7149 if (result
!= OriginParser::ValidOrigin
) {
7150 return PERSISTENCE_TYPE_INVALID
;
7152 return std::forward
<PersistenceTypeFunc
>(aPersistenceTypeFunc
)(spec
);
7155 mLeafName
= leafName
;
7158 mOriginalSuffix
= originalSuffix
;
7159 mPersistenceType
.init(persistenceType
);
7160 if (result
== OriginParser::ObsoleteOrigin
) {
7162 } else if (mSpec
.EqualsLiteral(kChromeOrigin
)) {
7171 nsresult
RepositoryOperationBase::ProcessRepository() {
7172 AssertIsOnIOThread();
7176 QM_TRY_INSPECT(const bool& exists
,
7177 MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory
, Exists
),
7178 QM_ASSERT_UNREACHABLE
);
7183 QM_TRY(CollectEachFileEntry(
7185 [](const auto& originFile
) -> Result
<mozilla::Ok
, nsresult
> {
7186 QM_TRY_INSPECT(const auto& leafName
,
7187 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7188 nsAutoString
, originFile
, GetLeafName
));
7190 // Unknown files during upgrade are allowed. Just warn if we find
7192 if (!IsOSMetadata(leafName
)) {
7193 UNKNOWN_FILE_WARNING(leafName
);
7196 return mozilla::Ok
{};
7198 [&self
= *this](const auto& originDir
) -> Result
<mozilla::Ok
, nsresult
> {
7199 OriginProps
originProps(WrapMovingNotNullUnchecked(originDir
));
7200 QM_TRY(MOZ_TO_RESULT(originProps
.Init([&self
](const auto& aSpec
) {
7201 return self
.PersistenceTypeFromSpec(aSpec
);
7203 // Bypass invalid origins while upgrading
7204 QM_TRY(OkIf(originProps
.mType
!= OriginProps::eInvalid
), mozilla::Ok
{});
7206 if (originProps
.mType
!= OriginProps::eObsolete
) {
7207 QM_TRY_INSPECT(const bool& removed
,
7208 MOZ_TO_RESULT_INVOKE_MEMBER(
7209 self
, PrepareOriginDirectory
, originProps
));
7211 return mozilla::Ok
{};
7215 self
.mOriginProps
.AppendElement(std::move(originProps
));
7217 return mozilla::Ok
{};
7220 if (mOriginProps
.IsEmpty()) {
7224 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
7229 template <typename UpgradeMethod
>
7230 nsresult
RepositoryOperationBase::MaybeUpgradeClients(
7231 const OriginProps
& aOriginProps
, UpgradeMethod aMethod
) {
7232 AssertIsOnIOThread();
7233 MOZ_ASSERT(aMethod
);
7235 QuotaManager
* quotaManager
= QuotaManager::Get();
7236 MOZ_ASSERT(quotaManager
);
7238 QM_TRY(CollectEachFileEntry(
7239 *aOriginProps
.mDirectory
,
7240 [](const auto& file
) -> Result
<mozilla::Ok
, nsresult
> {
7242 const auto& leafName
,
7243 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString
, file
, GetLeafName
));
7245 if (!IsOriginMetadata(leafName
) && !IsTempMetadata(leafName
)) {
7246 UNKNOWN_FILE_WARNING(leafName
);
7249 return mozilla::Ok
{};
7251 [quotaManager
, &aMethod
,
7252 &self
= *this](const auto& dir
) -> Result
<mozilla::Ok
, nsresult
> {
7254 const auto& leafName
,
7255 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString
, dir
, GetLeafName
));
7257 QM_TRY_INSPECT(const bool& removed
,
7258 MOZ_TO_RESULT_INVOKE_MEMBER(self
, PrepareClientDirectory
,
7261 return mozilla::Ok
{};
7264 Client::Type clientType
;
7265 bool ok
= Client::TypeFromText(leafName
, clientType
, fallible
);
7267 UNKNOWN_FILE_WARNING(leafName
);
7268 return mozilla::Ok
{};
7271 Client
* client
= quotaManager
->GetClient(clientType
);
7274 QM_TRY(MOZ_TO_RESULT((client
->*aMethod
)(dir
)));
7276 return mozilla::Ok
{};
7282 nsresult
RepositoryOperationBase::PrepareClientDirectory(
7283 nsIFile
* aFile
, const nsAString
& aLeafName
, bool& aRemoved
) {
7284 AssertIsOnIOThread();
7290 nsresult
CreateOrUpgradeDirectoryMetadataHelper::Init() {
7291 AssertIsOnIOThread();
7292 MOZ_ASSERT(mDirectory
);
7294 const auto maybeLegacyPersistenceType
=
7295 LegacyPersistenceTypeFromFile(*mDirectory
, fallible
);
7296 QM_TRY(OkIf(maybeLegacyPersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
7298 mLegacyPersistenceType
.init(maybeLegacyPersistenceType
.value());
7303 Maybe
<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType
>
7304 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
7305 nsIFile
& aFile
, const fallible_t
&) {
7306 nsAutoString leafName
;
7307 MOZ_ALWAYS_SUCCEEDS(aFile
.GetLeafName(leafName
));
7309 if (leafName
.Equals(u
"persistent"_ns
)) {
7310 return Some(LegacyPersistenceType::Persistent
);
7313 if (leafName
.Equals(u
"temporary"_ns
)) {
7314 return Some(LegacyPersistenceType::Temporary
);
7321 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
7322 const nsCString
& aSpec
) {
7323 if (QuotaManager::IsOriginInternal(aSpec
)) {
7324 return PERSISTENCE_TYPE_PERSISTENT
;
7327 return PERSISTENCE_TYPE_DEFAULT
;
7330 PersistenceType
CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
7331 const nsCString
& aSpec
) {
7332 switch (*mLegacyPersistenceType
) {
7333 case LegacyPersistenceType::Persistent
:
7334 return PersistenceTypeFromLegacyPersistentSpec(aSpec
);
7335 case LegacyPersistenceType::Temporary
:
7336 return PERSISTENCE_TYPE_TEMPORARY
;
7338 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
7341 nsresult
CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
7342 nsIFile
* aDirectory
) {
7343 AssertIsOnIOThread();
7344 MOZ_ASSERT(aDirectory
);
7347 const auto& metadataFile
,
7348 CloneFileAndAppend(*aDirectory
, nsLiteralString(METADATA_FILE_NAME
)));
7350 QM_TRY_INSPECT(const bool& exists
,
7351 MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile
, Exists
));
7354 // Directory structure upgrade needed.
7355 // Move all files to IDB specific directory.
7357 nsString idbDirectoryName
;
7358 QM_TRY(OkIf(Client::TypeToText(Client::IDB
, idbDirectoryName
, fallible
)),
7361 QM_TRY_INSPECT(const auto& idbDirectory
,
7362 CloneFileAndAppend(*aDirectory
, idbDirectoryName
));
7364 // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF
7365 // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the
7366 // idb directory shouldn't exist during the upgrade and the upgrade runs
7367 // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok
7369 QM_TRY(QM_OR_ELSE_WARN_IF(
7371 MOZ_TO_RESULT(idbDirectory
->Create(nsIFile::DIRECTORY_TYPE
, 0755)),
7373 IsSpecificError
<NS_ERROR_FILE_ALREADY_EXISTS
>,
7375 ([&idbDirectory
](const nsresult rv
) -> Result
<Ok
, nsresult
> {
7377 const bool& isDirectory
,
7378 MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory
, IsDirectory
));
7380 QM_TRY(OkIf(isDirectory
), Err(NS_ERROR_UNEXPECTED
));
7385 QM_TRY(CollectEachFile(
7387 [&idbDirectory
, &idbDirectoryName
](
7388 const nsCOMPtr
<nsIFile
>& file
) -> Result
<Ok
, nsresult
> {
7389 QM_TRY_INSPECT(const auto& leafName
,
7390 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString
, file
,
7393 if (!leafName
.Equals(idbDirectoryName
)) {
7394 QM_TRY(MOZ_TO_RESULT(file
->MoveTo(idbDirectory
, u
""_ns
)));
7401 MOZ_TO_RESULT(metadataFile
->Create(nsIFile::NORMAL_FILE_TYPE
, 0644)));
7407 nsresult
CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
7408 OriginProps
& aOriginProps
, bool* aRemoved
) {
7409 AssertIsOnIOThread();
7410 MOZ_ASSERT(aRemoved
);
7412 if (*mLegacyPersistenceType
== LegacyPersistenceType::Persistent
) {
7413 QM_TRY(MOZ_TO_RESULT(
7414 MaybeUpgradeOriginDirectory(aOriginProps
.mDirectory
.get())));
7416 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
7421 Nullable
<bool> isApp
;
7423 QM_WARNONLY_TRY_UNWRAP(
7424 const auto maybeDirectoryMetadata
,
7425 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps
.mDirectory
.get(),
7426 timestamp
, group
, origin
, isApp
)));
7427 if (!maybeDirectoryMetadata
) {
7428 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
7429 aOriginProps
.mNeedsRestore
= true;
7430 } else if (!isApp
.IsNull()) {
7431 aOriginProps
.mIgnore
= true;
7439 nsresult
CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
7440 const OriginProps
& aOriginProps
) {
7441 AssertIsOnIOThread();
7443 if (*mLegacyPersistenceType
== LegacyPersistenceType::Persistent
) {
7444 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7445 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7446 aOriginProps
.mOriginMetadata
)));
7448 // Move internal origins to new persistent storage.
7449 if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps
.mSpec
) ==
7450 PERSISTENCE_TYPE_PERSISTENT
) {
7451 if (!mPermanentStorageDir
) {
7452 QuotaManager
* quotaManager
= QuotaManager::Get();
7453 MOZ_ASSERT(quotaManager
);
7455 const nsString
& permanentStoragePath
=
7456 quotaManager
->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT
);
7458 QM_TRY_UNWRAP(mPermanentStorageDir
,
7459 QM_NewLocalFile(permanentStoragePath
));
7462 const nsAString
& leafName
= aOriginProps
.mLeafName
;
7464 QM_TRY_INSPECT(const auto& newDirectory
,
7465 CloneFileAndAppend(*mPermanentStorageDir
, leafName
));
7467 QM_TRY_INSPECT(const bool& exists
,
7468 MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory
, Exists
));
7471 QM_WARNING("Found %s in storage/persistent and storage/permanent !",
7472 NS_ConvertUTF16toUTF8(leafName
).get());
7474 QM_TRY(MOZ_TO_RESULT(
7475 aOriginProps
.mDirectory
->Remove(/* recursive */ true)));
7477 QM_TRY(MOZ_TO_RESULT(
7478 aOriginProps
.mDirectory
->MoveTo(mPermanentStorageDir
, u
""_ns
)));
7481 } else if (aOriginProps
.mNeedsRestore
) {
7482 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7483 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7484 aOriginProps
.mOriginMetadata
)));
7485 } else if (!aOriginProps
.mIgnore
) {
7486 QM_TRY_INSPECT(const auto& file
,
7487 CloneFileAndAppend(*aOriginProps
.mDirectory
,
7488 nsLiteralString(METADATA_FILE_NAME
)));
7490 QM_TRY_INSPECT(const auto& stream
,
7491 GetBinaryOutputStream(*file
, FileFlag::Append
));
7495 // Currently unused (used to be isApp).
7496 QM_TRY(MOZ_TO_RESULT(stream
->WriteBoolean(false)));
7502 nsresult
UpgradeStorageHelperBase::Init() {
7503 AssertIsOnIOThread();
7504 MOZ_ASSERT(mDirectory
);
7506 const auto maybePersistenceType
=
7507 PersistenceTypeFromFile(*mDirectory
, fallible
);
7508 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
7510 mPersistenceType
.init(maybePersistenceType
.value());
7515 PersistenceType
UpgradeStorageHelperBase::PersistenceTypeFromSpec(
7516 const nsCString
& aSpec
) {
7517 // There's no moving of origin directories between repositories like in the
7518 // CreateOrUpgradeDirectoryMetadataHelper
7519 return *mPersistenceType
;
7522 nsresult
UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
7523 OriginProps
& aOriginProps
, bool* aRemoved
) {
7524 AssertIsOnIOThread();
7525 MOZ_ASSERT(aRemoved
);
7530 Nullable
<bool> isApp
;
7532 QM_WARNONLY_TRY_UNWRAP(
7533 const auto maybeDirectoryMetadata
,
7534 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps
.mDirectory
.get(),
7535 timestamp
, group
, origin
, isApp
)));
7536 if (!maybeDirectoryMetadata
|| isApp
.IsNull()) {
7537 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
7538 aOriginProps
.mNeedsRestore
= true;
7540 aOriginProps
.mTimestamp
= timestamp
;
7547 nsresult
UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
7548 const OriginProps
& aOriginProps
) {
7549 AssertIsOnIOThread();
7551 // This handles changes in origin string generation from nsIPrincipal,
7552 // especially the change from: appId+inMozBrowser+originNoSuffix
7553 // to: origin (with origin suffix).
7554 QM_TRY_INSPECT(const bool& renamed
, MaybeRenameOrigin(aOriginProps
));
7559 if (aOriginProps
.mNeedsRestore
) {
7560 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7561 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7562 aOriginProps
.mOriginMetadata
)));
7565 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7566 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7567 /* aPersisted */ false, aOriginProps
.mOriginMetadata
)));
7572 nsresult
UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
7573 const OriginProps
& aOriginProps
) {
7574 AssertIsOnIOThread();
7576 // The Cache API was creating top level morgue directories by accident for
7577 // a short time in nightly. This unfortunately prevents all storage from
7578 // working. So recover these profiles permanently by removing these corrupt
7579 // directories as part of this upgrade.
7581 QM_TRY_INSPECT(const auto& morgueDir
,
7582 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7583 nsCOMPtr
<nsIFile
>, *aOriginProps
.mDirectory
, Clone
));
7585 QM_TRY(MOZ_TO_RESULT(morgueDir
->Append(u
"morgue"_ns
)));
7587 QM_TRY_INSPECT(const bool& exists
,
7588 MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir
, Exists
));
7591 QM_WARNING("Deleting accidental morgue directory!");
7593 QM_TRY(MOZ_TO_RESULT(morgueDir
->Remove(/* recursive */ true)));
7599 Result
<bool, nsresult
> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
7600 const OriginProps
& aOriginProps
) {
7601 AssertIsOnIOThread();
7603 // TODO: This method was empty for some time due to accidental changes done
7604 // in bug 1320404. This led to renaming of origin directories like:
7605 // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
7607 // https+++developer.cdn.mozilla.net^inBrowser=1
7608 // instead of just removing them.
7610 const nsCString
& originalSuffix
= aOriginProps
.mOriginalSuffix
;
7611 if (!originalSuffix
.IsEmpty()) {
7612 MOZ_ASSERT(originalSuffix
[0] == '^');
7614 if (!URLParams::Parse(
7615 Substring(originalSuffix
, 1, originalSuffix
.Length() - 1),
7616 [](const nsAString
& aName
, const nsAString
& aValue
) {
7617 if (aName
.EqualsLiteral("appId")) {
7623 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps
)));
7632 nsresult
UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
7633 OriginProps
& aOriginProps
, bool* aRemoved
) {
7634 AssertIsOnIOThread();
7635 MOZ_ASSERT(aRemoved
);
7637 QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps
)));
7639 QM_TRY(MOZ_TO_RESULT(
7640 MaybeUpgradeClients(aOriginProps
, &Client::UpgradeStorageFrom1_0To2_0
)));
7642 QM_TRY_INSPECT(const bool& removed
, MaybeRemoveAppsData(aOriginProps
));
7651 Nullable
<bool> isApp
;
7652 QM_WARNONLY_TRY_UNWRAP(
7653 const auto maybeDirectoryMetadata
,
7654 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps
.mDirectory
.get(),
7655 timestamp
, group
, origin
, isApp
)));
7656 if (!maybeDirectoryMetadata
|| isApp
.IsNull()) {
7657 aOriginProps
.mNeedsRestore
= true;
7661 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2
,
7662 MOZ_TO_RESULT(GetDirectoryMetadata2(
7663 aOriginProps
.mDirectory
.get(), timestamp
, suffix
,
7664 group
, origin
, isApp
.SetValue())));
7665 if (!maybeDirectoryMetadata2
) {
7666 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
7667 aOriginProps
.mNeedsRestore2
= true;
7669 aOriginProps
.mTimestamp
= timestamp
;
7676 nsresult
UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
7677 const OriginProps
& aOriginProps
) {
7678 AssertIsOnIOThread();
7680 // This handles changes in origin string generation from nsIPrincipal,
7681 // especially the stripping of obsolete origin attributes like addonId.
7682 QM_TRY_INSPECT(const bool& renamed
, MaybeRenameOrigin(aOriginProps
));
7687 if (aOriginProps
.mNeedsRestore
) {
7688 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7689 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7690 aOriginProps
.mOriginMetadata
)));
7693 if (aOriginProps
.mNeedsRestore2
) {
7694 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7695 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7696 /* aPersisted */ false, aOriginProps
.mOriginMetadata
)));
7702 nsresult
UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
7703 OriginProps
& aOriginProps
, bool* aRemoved
) {
7704 AssertIsOnIOThread();
7705 MOZ_ASSERT(aRemoved
);
7707 QM_TRY(MOZ_TO_RESULT(
7708 MaybeUpgradeClients(aOriginProps
, &Client::UpgradeStorageFrom2_0To2_1
)));
7713 Nullable
<bool> isApp
;
7714 QM_WARNONLY_TRY_UNWRAP(
7715 const auto maybeDirectoryMetadata
,
7716 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps
.mDirectory
.get(),
7717 timestamp
, group
, origin
, isApp
)));
7718 if (!maybeDirectoryMetadata
|| isApp
.IsNull()) {
7719 aOriginProps
.mNeedsRestore
= true;
7723 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2
,
7724 MOZ_TO_RESULT(GetDirectoryMetadata2(
7725 aOriginProps
.mDirectory
.get(), timestamp
, suffix
,
7726 group
, origin
, isApp
.SetValue())));
7727 if (!maybeDirectoryMetadata2
) {
7728 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
7729 aOriginProps
.mNeedsRestore2
= true;
7731 aOriginProps
.mTimestamp
= timestamp
;
7738 nsresult
UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
7739 const OriginProps
& aOriginProps
) {
7740 AssertIsOnIOThread();
7742 if (aOriginProps
.mNeedsRestore
) {
7743 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7744 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7745 aOriginProps
.mOriginMetadata
)));
7748 if (aOriginProps
.mNeedsRestore2
) {
7749 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7750 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7751 /* aPersisted */ false, aOriginProps
.mOriginMetadata
)));
7757 nsresult
UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
7758 OriginProps
& aOriginProps
, bool* aRemoved
) {
7759 AssertIsOnIOThread();
7760 MOZ_ASSERT(aRemoved
);
7762 QM_TRY(MOZ_TO_RESULT(
7763 MaybeUpgradeClients(aOriginProps
, &Client::UpgradeStorageFrom2_1To2_2
)));
7768 Nullable
<bool> isApp
;
7769 QM_WARNONLY_TRY_UNWRAP(
7770 const auto maybeDirectoryMetadata
,
7771 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps
.mDirectory
.get(),
7772 timestamp
, group
, origin
, isApp
)));
7773 if (!maybeDirectoryMetadata
|| isApp
.IsNull()) {
7774 aOriginProps
.mNeedsRestore
= true;
7778 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2
,
7779 MOZ_TO_RESULT(GetDirectoryMetadata2(
7780 aOriginProps
.mDirectory
.get(), timestamp
, suffix
,
7781 group
, origin
, isApp
.SetValue())));
7782 if (!maybeDirectoryMetadata2
) {
7783 aOriginProps
.mTimestamp
= GetOriginLastModifiedTime(aOriginProps
);
7784 aOriginProps
.mNeedsRestore2
= true;
7786 aOriginProps
.mTimestamp
= timestamp
;
7793 nsresult
UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
7794 const OriginProps
& aOriginProps
) {
7795 AssertIsOnIOThread();
7797 if (aOriginProps
.mNeedsRestore
) {
7798 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7799 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7800 aOriginProps
.mOriginMetadata
)));
7803 if (aOriginProps
.mNeedsRestore2
) {
7804 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7805 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7806 /* aPersisted */ false, aOriginProps
.mOriginMetadata
)));
7812 nsresult
UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
7813 nsIFile
* aFile
, const nsAString
& aLeafName
, bool& aRemoved
) {
7814 AssertIsOnIOThread();
7816 if (Client::IsDeprecatedClient(aLeafName
)) {
7817 QM_WARNING("Deleting deprecated %s client!",
7818 NS_ConvertUTF16toUTF8(aLeafName
).get());
7820 QM_TRY(MOZ_TO_RESULT(aFile
->Remove(true)));
7830 nsresult
RestoreDirectoryMetadata2Helper::Init() {
7831 AssertIsOnIOThread();
7832 MOZ_ASSERT(mDirectory
);
7834 nsCOMPtr
<nsIFile
> parentDir
;
7835 QM_TRY(MOZ_TO_RESULT(mDirectory
->GetParent(getter_AddRefs(parentDir
))));
7837 const auto maybePersistenceType
=
7838 PersistenceTypeFromFile(*parentDir
, fallible
);
7839 QM_TRY(OkIf(maybePersistenceType
.isSome()), Err(NS_ERROR_FAILURE
));
7841 mPersistenceType
.init(maybePersistenceType
.value());
7846 nsresult
RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
7847 OriginProps
originProps(WrapMovingNotNull(mDirectory
));
7848 QM_TRY(MOZ_TO_RESULT(originProps
.Init(
7849 [&self
= *this](const auto& aSpec
) { return *self
.mPersistenceType
; })));
7851 QM_TRY(OkIf(originProps
.mType
!= OriginProps::eInvalid
), NS_ERROR_FAILURE
);
7853 originProps
.mTimestamp
= GetOriginLastModifiedTime(originProps
);
7855 mOriginProps
.AppendElement(std::move(originProps
));
7857 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
7862 nsresult
RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
7863 const OriginProps
& aOriginProps
) {
7864 AssertIsOnIOThread();
7866 // We don't have any approach to restore aPersisted, so reset it to false.
7867 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7868 *aOriginProps
.mDirectory
, aOriginProps
.mTimestamp
,
7869 /* aPersisted */ false, aOriginProps
.mOriginMetadata
)));
7874 } // namespace mozilla::dom::quota