tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ActorsParent.cpp (347056B)


      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/. */
      6 
      7 #include "ActorsParent.h"
      8 
      9 // Local includes
     10 #include "CanonicalQuotaObject.h"
     11 #include "ClientUsageArray.h"
     12 #include "DirectoryMetadata.h"
     13 #include "FirstInitializationAttemptsImpl.h"
     14 #include "Flatten.h"
     15 #include "GroupInfo.h"
     16 #include "GroupInfoPair.h"
     17 #include "GroupInfoPairImpl.h"
     18 #include "NormalOriginOperationBase.h"
     19 #include "OpenClientDirectoryUtils.h"
     20 #include "OriginInfo.h"
     21 #include "OriginOperationBase.h"
     22 #include "OriginOperations.h"
     23 #include "OriginParser.h"
     24 #include "OriginScope.h"
     25 #include "QuotaCommon.h"
     26 #include "QuotaManager.h"
     27 #include "QuotaPrefs.h"
     28 #include "ResolvableNormalOriginOp.h"
     29 #include "SanitizationUtils.h"
     30 #include "ScopedLogExtraInfo.h"
     31 #include "UsageInfo.h"
     32 
     33 // Global includes
     34 #include <algorithm>
     35 #include <cinttypes>
     36 #include <cstdint>
     37 #include <cstdlib>
     38 #include <cstring>
     39 #include <functional>
     40 #include <new>
     41 #include <numeric>
     42 #include <tuple>
     43 #include <type_traits>
     44 #include <utility>
     45 
     46 #include "DirectoryLockImpl.h"
     47 #include "ErrorList.h"
     48 #include "MainThreadUtils.h"
     49 #include "mozIStorageAsyncConnection.h"
     50 #include "mozIStorageConnection.h"
     51 #include "mozIStorageService.h"
     52 #include "mozIStorageStatement.h"
     53 #include "mozStorageCID.h"
     54 #include "mozStorageHelper.h"
     55 #include "mozilla/AlreadyAddRefed.h"
     56 #include "mozilla/AppShutdown.h"
     57 #include "mozilla/Assertions.h"
     58 #include "mozilla/Atomics.h"
     59 #include "mozilla/Attributes.h"
     60 #include "mozilla/AutoRestore.h"
     61 #include "mozilla/BasePrincipal.h"
     62 #include "mozilla/CheckedInt.h"
     63 #include "mozilla/CondVar.h"
     64 #include "mozilla/GeckoTrace.h"
     65 #include "mozilla/InitializedOnce.h"
     66 #include "mozilla/Logging.h"
     67 #include "mozilla/Maybe.h"
     68 #include "mozilla/Mutex.h"
     69 #include "mozilla/NotNull.h"
     70 #include "mozilla/Now.h"
     71 #include "mozilla/OriginAttributes.h"
     72 #include "mozilla/Preferences.h"
     73 #include "mozilla/RefPtr.h"
     74 #include "mozilla/Result.h"
     75 #include "mozilla/ResultExtensions.h"
     76 #include "mozilla/ScopeExit.h"
     77 #include "mozilla/SpinEventLoopUntil.h"
     78 #include "mozilla/StaticPrefs_dom.h"
     79 #include "mozilla/StaticPtr.h"
     80 #include "mozilla/StorageOriginAttributes.h"
     81 #include "mozilla/SystemPrincipal.h"
     82 #include "mozilla/TimeStamp.h"
     83 #include "mozilla/dom/FileSystemQuotaClientFactory.h"
     84 #include "mozilla/dom/FlippedOnce.h"
     85 #include "mozilla/dom/IndexedDatabaseManager.h"
     86 #include "mozilla/dom/LocalStorageCommon.h"
     87 #include "mozilla/dom/StorageDBUpdater.h"
     88 #include "mozilla/dom/cache/QuotaClient.h"
     89 #include "mozilla/dom/indexedDB/ActorsParent.h"
     90 #include "mozilla/dom/ipc/IdType.h"
     91 #include "mozilla/dom/localstorage/ActorsParent.h"
     92 #include "mozilla/dom/quota/ArtificialFailure.h"
     93 #include "mozilla/dom/quota/AssertionsImpl.h"
     94 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
     95 #include "mozilla/dom/quota/Client.h"
     96 #include "mozilla/dom/quota/ClientDirectoryLock.h"
     97 #include "mozilla/dom/quota/ClientDirectoryLockHandle.h"
     98 #include "mozilla/dom/quota/Config.h"
     99 #include "mozilla/dom/quota/Constants.h"
    100 #include "mozilla/dom/quota/Date.h"
    101 #include "mozilla/dom/quota/DirectoryLockInlines.h"
    102 #include "mozilla/dom/quota/FileUtils.h"
    103 #include "mozilla/dom/quota/MozPromiseUtils.h"
    104 #include "mozilla/dom/quota/OriginDirectoryLock.h"
    105 #include "mozilla/dom/quota/PersistenceType.h"
    106 #include "mozilla/dom/quota/PrincipalUtils.h"
    107 #include "mozilla/dom/quota/QuotaManagerImpl.h"
    108 #include "mozilla/dom/quota/QuotaManagerService.h"
    109 #include "mozilla/dom/quota/ResultExtensions.h"
    110 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
    111 #include "mozilla/dom/quota/StreamUtils.h"
    112 #include "mozilla/dom/quota/ThreadUtils.h"
    113 #include "mozilla/dom/quota/UniversalDirectoryLock.h"
    114 #include "mozilla/dom/simpledb/ActorsParent.h"
    115 #include "mozilla/fallible.h"
    116 #include "mozilla/glean/DomQuotaMetrics.h"
    117 #include "mozilla/ipc/BackgroundChild.h"
    118 #include "mozilla/ipc/BackgroundParent.h"
    119 #include "mozilla/ipc/PBackgroundChild.h"
    120 #include "mozilla/ipc/ProtocolUtils.h"
    121 #include "mozilla/net/ExtensionProtocolHandler.h"
    122 #include "nsAppDirectoryServiceDefs.h"
    123 #include "nsBaseHashtable.h"
    124 #include "nsCOMPtr.h"
    125 #include "nsCRTGlue.h"
    126 #include "nsClassHashtable.h"
    127 #include "nsComponentManagerUtils.h"
    128 #include "nsContentUtils.h"
    129 #include "nsDebug.h"
    130 #include "nsDirectoryServiceUtils.h"
    131 #include "nsError.h"
    132 #include "nsIBinaryInputStream.h"
    133 #include "nsIBinaryOutputStream.h"
    134 #include "nsIConsoleService.h"
    135 #include "nsIDUtils.h"
    136 #include "nsIDirectoryEnumerator.h"
    137 #include "nsIEventTarget.h"
    138 #include "nsIFile.h"
    139 #include "nsIFileStreams.h"
    140 #include "nsIInputStream.h"
    141 #include "nsIObjectInputStream.h"
    142 #include "nsIObjectOutputStream.h"
    143 #include "nsIObserver.h"
    144 #include "nsIObserverService.h"
    145 #include "nsIOutputStream.h"
    146 #include "nsIPlatformInfo.h"
    147 #include "nsIPrincipal.h"
    148 #include "nsIQuotaManagerServiceInternal.h"
    149 #include "nsIQuotaRequests.h"
    150 #include "nsIQuotaUtilsService.h"
    151 #include "nsIRunnable.h"
    152 #include "nsISupports.h"
    153 #include "nsIThread.h"
    154 #include "nsITimer.h"
    155 #include "nsIURI.h"
    156 #include "nsIWidget.h"
    157 #include "nsLiteralString.h"
    158 #include "nsNetUtil.h"
    159 #include "nsPrintfCString.h"
    160 #include "nsServiceManagerUtils.h"
    161 #include "nsStandardURL.h"
    162 #include "nsString.h"
    163 #include "nsStringFlags.h"
    164 #include "nsStringFwd.h"
    165 #include "nsTArray.h"
    166 #include "nsTHashtable.h"
    167 #include "nsTLiteralString.h"
    168 #include "nsTPromiseFlatString.h"
    169 #include "nsTStringRepr.h"
    170 #include "nsThreadUtils.h"
    171 #include "nsURLHelper.h"
    172 #include "nsXPCOM.h"
    173 #include "nsXPCOMCID.h"
    174 #include "nsXULAppAPI.h"
    175 #include "prinrval.h"
    176 #include "prio.h"
    177 #include "prtime.h"
    178 
    179 // The amount of time, in milliseconds, that our IO thread will stay alive
    180 // after the last event it processes.
    181 #define DEFAULT_THREAD_TIMEOUT_MS 30000
    182 
    183 /**
    184 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
    185 * the crash timeout.
    186 */
    187 #define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
    188 
    189 /**
    190 * Automatically crash the browser if shutdown of a quota client takes this
    191 * long. We've chosen a value that is long enough that it is unlikely for the
    192 * problem to be falsely triggered by slow system I/O.  We've also chosen a
    193 * value long enough so that automated tests should time out and fail if
    194 * shutdown of a quota client takes too long.  Also, this value is long enough
    195 * so that testers can notice the timeout; we want to know about the timeouts,
    196 * not hide them. On the other hand this value is less than 60 seconds which is
    197 * used by nsTerminator to crash a hung main process.
    198 */
    199 #define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
    200 
    201 static_assert(
    202    SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
    203    "The kill actors timeout must be shorter than the crash browser one.");
    204 
    205 // profile-before-change, when we need to shut down quota manager
    206 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
    207 
    208 #define KB *1024ULL
    209 #define MB *1024ULL KB
    210 #define GB *1024ULL MB
    211 
    212 namespace mozilla::dom::quota {
    213 
    214 using namespace mozilla::ipc;
    215 
    216 // We want profiles to be platform-independent so we always need to replace
    217 // the same characters on every platform. Windows has the most extensive set
    218 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
    219 // FILE_PATH_SEPARATOR.
    220 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
    221 const char16_t QuotaManager::kReplaceChars16[] =
    222    u"" CONTROL_CHARACTERS "/:*?\"<>|\\";
    223 
    224 namespace {
    225 
    226 /*******************************************************************************
    227 * Constants
    228 ******************************************************************************/
    229 
    230 const uint32_t kSQLitePageSizeOverride = 512;
    231 
    232 // Important version history:
    233 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
    234 //   which caused Firefox 57 release concerns because the major schema upgrade
    235 //   means anyone downgrading to Firefox 56 will experience a non-operational
    236 //   QuotaManager and all of its clients.
    237 // - Bug 1404344 got very concerned about that and so we decided to effectively
    238 //   rename 3.0 to 2.1, effective in Firefox 57.  This works because post
    239 //   storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
    240 //   increases.  It also works because all the upgrade did was give the DOM
    241 //   Cache API QuotaClient an opportunity to create its newly added .padding
    242 //   files during initialization/upgrade, which isn't functionally necessary as
    243 //   that can be done on demand.
    244 
    245 // Major storage version. Bump for backwards-incompatible changes.
    246 // (The next major version should be 4 to distinguish from the Bug 1290481
    247 // downgrade snafu.)
    248 const uint32_t kMajorStorageVersion = 2;
    249 
    250 // Minor storage version. Bump for backwards-compatible changes.
    251 const uint32_t kMinorStorageVersion = 3;
    252 
    253 // The storage version we store in the SQLite database is a (signed) 32-bit
    254 // integer. The major version is left-shifted 16 bits so the max value is
    255 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
    256 static_assert(kMajorStorageVersion <= 0xFFFF,
    257              "Major version needs to fit in 16 bits.");
    258 static_assert(kMinorStorageVersion <= 0xFFFF,
    259              "Minor version needs to fit in 16 bits.");
    260 
    261 const int32_t kStorageVersion =
    262    int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
    263 
    264 // See comments above about why these are a thing.
    265 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
    266 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
    267 
    268 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
    269 const char kIndexedDBOriginPrefix[] = "indexeddb://";
    270 const char kResourceOriginPrefix[] = "resource://";
    271 
    272 constexpr auto kStorageName = u"storage"_ns;
    273 
    274 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
    275 #define ARCHIVES_DIRECTORY_NAME u"archives"
    276 #define PERSISTENT_DIRECTORY_NAME u"persistent"
    277 #define PERMANENT_DIRECTORY_NAME u"permanent"
    278 #define TEMPORARY_DIRECTORY_NAME u"temporary"
    279 #define DEFAULT_DIRECTORY_NAME u"default"
    280 #define PRIVATE_DIRECTORY_NAME u"private"
    281 #define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed"
    282 
    283 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
    284 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
    285 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
    286 
    287 const int32_t kLocalStorageArchiveVersion = 4;
    288 
    289 const char kProfileDoChangeTopic[] = "profile-do-change";
    290 const char kContextualIdentityServiceLoadFinishedTopic[] =
    291    "contextual-identity-service-load-finished";
    292 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
    293 
    294 const int32_t kCacheVersion = 3;
    295 
    296 // Sentinel value written at the end of the metadata file to indicate that the
    297 // file includes the extended origin metadata format. The sentinel allows us to
    298 // safely distinguish upgraded files from older versions and maintain backward
    299 // compatibility without requiring a full storage migration. Older files that
    300 // do not contain the sentinel will be handled using a separate code path to
    301 // fill the new fields with default or safe values.
    302 const uint32_t kMetadataSentinel = 0xABCD1234;
    303 
    304 /******************************************************************************
    305 * SQLite functions
    306 ******************************************************************************/
    307 
    308 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
    309                           uint32_t aMinorStorageVersion) {
    310  return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
    311 }
    312 
    313 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
    314  return uint32_t(aStorageVersion >> 16);
    315 }
    316 
    317 nsresult CreateTables(mozIStorageConnection* aConnection) {
    318  AssertIsOnIOThread();
    319  MOZ_ASSERT(aConnection);
    320 
    321  // Table `database`
    322  QM_TRY(MOZ_TO_RESULT(
    323      aConnection->ExecuteSimpleSQL("CREATE TABLE database"
    324                                    "( cache_version INTEGER NOT NULL DEFAULT 0"
    325                                    ");"_ns)));
    326 
    327 #ifdef DEBUG
    328  {
    329    QM_TRY_INSPECT(const int32_t& storageVersion,
    330                   MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
    331 
    332    MOZ_ASSERT(storageVersion == 0);
    333  }
    334 #endif
    335 
    336  QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kStorageVersion)));
    337 
    338  return NS_OK;
    339 }
    340 
    341 Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
    342  AssertIsOnIOThread();
    343 
    344  QM_TRY_INSPECT(const auto& stmt,
    345                 CreateAndExecuteSingleStepStatement<
    346                     SingleStepResult::ReturnNullIfNoResult>(
    347                     aConnection, "SELECT cache_version FROM database"_ns));
    348 
    349  QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
    350 
    351  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
    352 }
    353 
    354 nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
    355                          int32_t aVersion) {
    356  AssertIsOnIOThread();
    357 
    358  QM_TRY_INSPECT(
    359      const auto& stmt,
    360      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    361          nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
    362          "UPDATE database SET cache_version = :version;"_ns));
    363 
    364  QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion)));
    365 
    366  QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
    367 
    368  return NS_OK;
    369 }
    370 
    371 nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
    372  AssertIsOnIOThread();
    373 
    374  // Table `cache`
    375  QM_TRY(MOZ_TO_RESULT(
    376      aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
    377                                   "( valid INTEGER NOT NULL DEFAULT 0"
    378                                   ", build_id TEXT NOT NULL DEFAULT ''"
    379                                   ");"_ns)));
    380 
    381  // Table `repository`
    382  QM_TRY(
    383      MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
    384                                                 "( id INTEGER PRIMARY KEY"
    385                                                 ", name TEXT NOT NULL"
    386                                                 ");"_ns)));
    387 
    388  // Table `origin`
    389  QM_TRY(MOZ_TO_RESULT(
    390      aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
    391                                   "( repository_id INTEGER NOT NULL"
    392                                   ", suffix TEXT"
    393                                   ", group_ TEXT NOT NULL"
    394                                   ", origin TEXT NOT NULL"
    395                                   ", client_usages TEXT NOT NULL"
    396                                   ", usage INTEGER NOT NULL"
    397                                   ", last_access_time INTEGER NOT NULL"
    398                                   ", last_maintenance_date INTEGER NOT NULL"
    399                                   ", accessed INTEGER NOT NULL"
    400                                   ", persisted INTEGER NOT NULL"
    401                                   ", PRIMARY KEY (repository_id, origin)"
    402                                   ", FOREIGN KEY (repository_id) "
    403                                   "REFERENCES repository(id) "
    404                                   ");"_ns)));
    405 
    406 #ifdef DEBUG
    407  {
    408    QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
    409    MOZ_ASSERT(cacheVersion == 0);
    410  }
    411 #endif
    412 
    413  QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion)));
    414 
    415  return NS_OK;
    416 }
    417 
    418 OkOrErr InvalidateCache(mozIStorageConnection& aConnection) {
    419  AssertIsOnIOThread();
    420 
    421  static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
    422  static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;
    423 
    424  QM_TRY(QM_OR_ELSE_WARN(
    425      // Expression.
    426      ([&]() -> OkOrErr {
    427        mozStorageTransaction transaction(&aConnection,
    428                                          /*aCommitOnComplete */ false);
    429 
    430        QM_TRY(QM_TO_RESULT(transaction.Start()));
    431        QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery)));
    432        QM_TRY(
    433            QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
    434        QM_TRY(QM_TO_RESULT(transaction.Commit()));
    435 
    436        return Ok{};
    437      }()),
    438      // Fallback.
    439      ([&](const QMResult& rv) -> OkOrErr {
    440        QM_TRY(
    441            QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
    442 
    443        return Ok{};
    444      })));
    445 
    446  return Ok{};
    447 }
    448 
    449 nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
    450  AssertIsOnIOThread();
    451 
    452  QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
    453      "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns)));
    454 
    455  QM_TRY(InvalidateCache(aConnection));
    456 
    457 #ifdef DEBUG
    458  {
    459    QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
    460 
    461    MOZ_ASSERT(cacheVersion == 1);
    462  }
    463 #endif
    464 
    465  QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2)));
    466 
    467  return NS_OK;
    468 }
    469 
    470 nsresult UpgradeCacheFrom2To3(mozIStorageConnection& aConnection) {
    471  AssertIsOnIOThread();
    472 
    473  QM_TRY(InvalidateCache(aConnection));
    474 
    475  QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
    476      "ALTER TABLE origin ADD COLUMN last_maintenance_date INTEGER NOT NULL"_ns)));
    477 
    478 #ifdef DEBUG
    479  {
    480    QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
    481 
    482    MOZ_ASSERT(cacheVersion == 2);
    483  }
    484 #endif
    485 
    486  QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 3)));
    487 
    488  return NS_OK;
    489 }
    490 
    491 Result<bool, nsresult> MaybeCreateOrUpgradeCache(
    492    mozIStorageConnection& aConnection) {
    493  GECKO_TRACE_SCOPE("dom::quota", "MaybeCreateOrUpgradeCache");
    494 
    495  bool cacheUsable = true;
    496 
    497  QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));
    498 
    499  if (cacheVersion > kCacheVersion) {
    500    cacheUsable = false;
    501  } else if (cacheVersion != kCacheVersion) {
    502    const bool newCache = !cacheVersion;
    503 
    504    mozStorageTransaction transaction(
    505        &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
    506 
    507    QM_TRY(MOZ_TO_RESULT(transaction.Start()));
    508 
    509    if (newCache) {
    510      QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection)));
    511 
    512 #ifdef DEBUG
    513      {
    514        QM_TRY_INSPECT(const int32_t& cacheVersion,
    515                       LoadCacheVersion(aConnection));
    516        MOZ_ASSERT(cacheVersion == kCacheVersion);
    517      }
    518 #endif
    519 
    520      QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
    521          nsLiteralCString("INSERT INTO cache (valid, build_id) "
    522                           "VALUES (0, '')"))));
    523 
    524      nsCOMPtr<mozIStorageStatement> insertStmt;
    525 
    526      for (const PersistenceType persistenceType : kAllPersistenceTypes) {
    527        if (insertStmt) {
    528          MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
    529        } else {
    530          QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    531                                        nsCOMPtr<mozIStorageStatement>,
    532                                        aConnection, CreateStatement,
    533                                        "INSERT INTO repository (id, name) "
    534                                        "VALUES (:id, :name)"_ns));
    535        }
    536 
    537        QM_TRY(MOZ_TO_RESULT(
    538            insertStmt->BindInt32ByName("id"_ns, persistenceType)));
    539 
    540        QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName(
    541            "name"_ns, PersistenceTypeToString(persistenceType))));
    542 
    543        QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()));
    544      }
    545    } else {
    546      // This logic needs to change next time we change the cache!
    547      static_assert(kCacheVersion == 3,
    548                    "Upgrade function needed due to cache version increase.");
    549 
    550      while (cacheVersion != kCacheVersion) {
    551        if (cacheVersion == 1) {
    552          QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection)));
    553        } else if (cacheVersion == 2) {
    554          QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom2To3(aConnection)));
    555        } else {
    556          QM_FAIL(Err(NS_ERROR_FAILURE), []() {
    557            QM_WARNING(
    558                "Unable to initialize cache, no upgrade path is "
    559                "available!");
    560          });
    561        }
    562 
    563        QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
    564      }
    565 
    566      MOZ_ASSERT(cacheVersion == kCacheVersion);
    567    }
    568 
    569    QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
    570  }
    571 
    572  return cacheUsable;
    573 }
    574 
    575 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
    576    nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
    577  AssertIsOnIOThread();
    578 
    579  // Check if the old database exists at all.
    580  QM_TRY_INSPECT(const bool& exists,
    581                 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists));
    582 
    583  if (!exists) {
    584    // webappsstore.sqlite doesn't exist, return a null connection.
    585    return nsCOMPtr<mozIStorageConnection>{};
    586  }
    587 
    588  QM_TRY_INSPECT(const bool& isDirectory,
    589                 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, IsDirectory));
    590 
    591  if (isDirectory) {
    592    QM_WARNING("webappsstore.sqlite is not a file!");
    593    return nsCOMPtr<mozIStorageConnection>{};
    594  }
    595 
    596  QM_TRY_INSPECT(const auto& connection,
    597                 QM_OR_ELSE_WARN_IF(
    598                     // Expression.
    599                     MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    600                         nsCOMPtr<mozIStorageConnection>, aStorageService,
    601                         OpenUnsharedDatabase, &aWebAppsStoreFile,
    602                         mozIStorageService::CONNECTION_DEFAULT),
    603                     // Predicate.
    604                     IsDatabaseCorruptionError,
    605                     // Fallback. Don't throw an error, leave a corrupted
    606                     // webappsstore database as it is.
    607                     ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
    608 
    609  if (connection) {
    610    // Don't propagate an error, leave a non-updateable webappsstore database as
    611    // it is.
    612    QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
    613           nsCOMPtr<mozIStorageConnection>{});
    614  }
    615 
    616  return connection;
    617 }
    618 
    619 Result<nsCOMPtr<nsIFile>, QMResult> GetLocalStorageArchiveFile(
    620    const nsAString& aDirectoryPath) {
    621  AssertIsOnIOThread();
    622  MOZ_ASSERT(!aDirectoryPath.IsEmpty());
    623 
    624  QM_TRY_UNWRAP(auto lsArchiveFile,
    625                QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath)));
    626 
    627  QM_TRY(QM_TO_RESULT(
    628      lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));
    629 
    630  return lsArchiveFile;
    631 }
    632 
    633 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile(
    634    const nsAString& aDirectoryPath) {
    635  AssertIsOnIOThread();
    636  MOZ_ASSERT(!aDirectoryPath.IsEmpty());
    637 
    638  QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));
    639 
    640  QM_TRY(MOZ_TO_RESULT(
    641      lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
    642 
    643  return lsArchiveTmpFile;
    644 }
    645 
    646 Result<bool, nsresult> IsLocalStorageArchiveInitialized(
    647    mozIStorageConnection& aConnection) {
    648  AssertIsOnIOThread();
    649 
    650  QM_TRY_RETURN(
    651      MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns));
    652 }
    653 
    654 nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) {
    655  AssertIsOnIOThread();
    656  MOZ_ASSERT(aConnection);
    657 
    658 #ifdef DEBUG
    659  {
    660    QM_TRY_INSPECT(const auto& initialized,
    661                   IsLocalStorageArchiveInitialized(*aConnection));
    662    MOZ_ASSERT(!initialized);
    663  }
    664 #endif
    665 
    666  QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
    667      "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns)));
    668 
    669  QM_TRY_INSPECT(
    670      const auto& stmt,
    671      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    672          nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
    673          "INSERT INTO database (version) VALUES (:version)"_ns));
    674 
    675  QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0)));
    676  QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
    677 
    678  return NS_OK;
    679 }
    680 
    681 Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
    682    mozIStorageConnection& aConnection) {
    683  AssertIsOnIOThread();
    684 
    685  QM_TRY_INSPECT(const auto& stmt,
    686                 CreateAndExecuteSingleStepStatement<
    687                     SingleStepResult::ReturnNullIfNoResult>(
    688                     aConnection, "SELECT version FROM database"_ns));
    689 
    690  QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
    691 
    692  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
    693 }
    694 
    695 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
    696                                        int32_t aVersion) {
    697  AssertIsOnIOThread();
    698  MOZ_ASSERT(aConnection);
    699 
    700  nsCOMPtr<mozIStorageStatement> stmt;
    701  nsresult rv = aConnection->CreateStatement(
    702      "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
    703  if (NS_WARN_IF(NS_FAILED(rv))) {
    704    return rv;
    705  }
    706 
    707  rv = stmt->BindInt32ByName("version"_ns, aVersion);
    708  if (NS_WARN_IF(NS_FAILED(rv))) {
    709    return rv;
    710  }
    711 
    712  rv = stmt->Execute();
    713  if (NS_WARN_IF(NS_FAILED(rv))) {
    714    return rv;
    715  }
    716 
    717  return NS_OK;
    718 }
    719 
    720 template <typename FileFunc, typename DirectoryFunc>
    721 Result<mozilla::Ok, nsresult> CollectEachFileEntry(
    722    nsIFile& aDirectory, const FileFunc& aFileFunc,
    723    const DirectoryFunc& aDirectoryFunc) {
    724  AssertIsOnIOThread();
    725 
    726  return CollectEachFile(
    727      aDirectory,
    728      [&aFileFunc, &aDirectoryFunc](
    729          const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
    730        QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
    731 
    732        switch (dirEntryKind) {
    733          case nsIFileKind::ExistsAsDirectory:
    734            return aDirectoryFunc(file);
    735 
    736          case nsIFileKind::ExistsAsFile:
    737            return aFileFunc(file);
    738 
    739          case nsIFileKind::DoesNotExist:
    740            // Ignore files that got removed externally while iterating.
    741            break;
    742        }
    743 
    744        return Ok{};
    745      });
    746 }
    747 
    748 /******************************************************************************
    749 * Quota manager class declarations
    750 ******************************************************************************/
    751 
    752 }  // namespace
    753 
    754 class QuotaManager::Observer final : public nsIObserver {
    755  static Observer* sInstance;
    756 
    757  bool mPendingProfileChange;
    758  bool mShutdownComplete;
    759 
    760 public:
    761  static nsresult Initialize();
    762 
    763  static nsIObserver* GetInstance();
    764 
    765  static void ShutdownCompleted();
    766 
    767 private:
    768  Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
    769    MOZ_ASSERT(NS_IsMainThread());
    770  }
    771 
    772  ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
    773 
    774  nsresult Init();
    775 
    776  nsresult Shutdown();
    777 
    778  NS_DECL_ISUPPORTS
    779  NS_DECL_NSIOBSERVER
    780 };
    781 
    782 namespace {
    783 
    784 /*******************************************************************************
    785 * Local class declarations
    786 ******************************************************************************/
    787 
    788 }  // namespace
    789 
    790 namespace {
    791 
    792 class CollectOriginsHelper final : public Runnable {
    793  uint64_t mMinSizeToBeFreed;
    794 
    795  Mutex& mMutex;
    796  CondVar mCondVar;
    797 
    798  // The members below are protected by mMutex.
    799  nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
    800  uint64_t mSizeToBeFreed;
    801  bool mWaiting;
    802 
    803 public:
    804  CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
    805 
    806  // Blocks the current thread until origins are collected on the main thread.
    807  // The returned value contains an aggregate size of those origins.
    808  int64_t BlockAndReturnOriginsForEviction(
    809      nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);
    810 
    811 private:
    812  ~CollectOriginsHelper() = default;
    813 
    814  NS_IMETHOD
    815  Run() override;
    816 };
    817 
    818 /*******************************************************************************
    819 * Other class declarations
    820 ******************************************************************************/
    821 
    822 class RecordTimeDeltaHelper final : public Runnable {
    823  const mozilla::glean::impl::Labeled<
    824      mozilla::glean::impl::TimingDistributionMetric, DynamicLabel>& mMetric;
    825 
    826  // TimeStamps that are set on the IO thread.
    827  LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
    828  LazyInitializedOnceNotNull<const TimeStamp> mEndTime;
    829 
    830  // A TimeStamp that is set on the main thread.
    831  LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;
    832 
    833 public:
    834  explicit RecordTimeDeltaHelper(const mozilla::glean::impl::Labeled<
    835                                 mozilla::glean::impl::TimingDistributionMetric,
    836                                 DynamicLabel>& aMetric)
    837      : Runnable("dom::quota::RecordTimeDeltaHelper"), mMetric(aMetric) {}
    838 
    839  TimeStamp Start();
    840 
    841  TimeStamp End();
    842 
    843 private:
    844  ~RecordTimeDeltaHelper() = default;
    845 
    846  NS_DECL_NSIRUNNABLE
    847 };
    848 
    849 /*******************************************************************************
    850 * Helper classes
    851 ******************************************************************************/
    852 
    853 /*******************************************************************************
    854 * Helper Functions
    855 ******************************************************************************/
    856 
    857 // Return whether the group was actually updated.
    858 Result<bool, nsresult> MaybeUpdateGroupForOrigin(
    859    OriginMetadata& aOriginMetadata) {
    860  MOZ_ASSERT(!NS_IsMainThread());
    861 
    862  bool updated = false;
    863 
    864  if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
    865    if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
    866      aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
    867      updated = true;
    868    }
    869  } else {
    870    nsCOMPtr<nsIPrincipal> principal =
    871        BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin);
    872    QM_TRY(MOZ_TO_RESULT(principal));
    873 
    874    QM_TRY_INSPECT(const auto& baseDomain,
    875                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
    876                                                     GetBaseDomain));
    877 
    878    const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
    879 
    880    if (aOriginMetadata.mGroup != upToDateGroup) {
    881      aOriginMetadata.mGroup = upToDateGroup;
    882      updated = true;
    883    }
    884  }
    885 
    886  return updated;
    887 }
    888 
    889 Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin(
    890    FullOriginMetadata& aFullOriginMetadata) {
    891  GECKO_TRACE_SCOPE("dom::quota", "MaybeUpdateLastAccessTimeForOrigin");
    892 
    893  MOZ_ASSERT(!NS_IsMainThread());
    894 
    895  if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) {
    896    QuotaManager* quotaManager = QuotaManager::Get();
    897    MOZ_ASSERT(quotaManager);
    898 
    899    QM_TRY_INSPECT(const auto& metadataFile,
    900                   quotaManager->GetOriginDirectory(aFullOriginMetadata));
    901 
    902    QM_TRY(MOZ_TO_RESULT(
    903        metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
    904 
    905    QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
    906                                         metadataFile, GetLastModifiedTime));
    907 
    908    // Need to convert from milliseconds to microseconds.
    909    MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
    910    timestamp *= int64_t(PR_USEC_PER_MSEC);
    911 
    912    aFullOriginMetadata.mLastAccessTime = timestamp;
    913 
    914    return true;
    915  }
    916 
    917  return false;
    918 }
    919 
    920 }  // namespace
    921 
    922 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
    923  // Get leaf of file path
    924  for (const char* p = aFile; *p; ++p) {
    925    if (*p == '/' && *(p + 1)) {
    926      aFile = p + 1;
    927    }
    928  }
    929 
    930  nsContentUtils::LogSimpleConsoleError(
    931      NS_ConvertUTF8toUTF16(
    932          nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
    933      "quota"_ns,
    934      false /* Quota Manager is not active in private browsing mode */,
    935      true /* Quota Manager runs always in a chrome context */);
    936 }
    937 
    938 namespace {
    939 
    940 bool gInvalidateQuotaCache = false;
    941 StaticAutoPtr<nsString> gBasePath;
    942 StaticAutoPtr<nsString> gStorageName;
    943 StaticAutoPtr<nsCString> gBuildId;
    944 
    945 #ifdef DEBUG
    946 bool gQuotaManagerInitialized = false;
    947 #endif
    948 
    949 StaticRefPtr<QuotaManager> gInstance;
    950 mozilla::Atomic<bool> gShutdown(false);
    951 
    952 // A time stamp that can only be accessed on the main thread.
    953 TimeStamp gLastOSWake;
    954 
    955 // XXX Move to QuotaManager once NormalOriginOperationBase is declared in a
    956 // separate and includable file.
    957 using NormalOriginOpArray =
    958    nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
    959 StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
    960 
    961 class StorageOperationBase {
    962 protected:
    963  struct OriginProps {
    964    enum Type { eChrome, eContent, eObsolete, eInvalid };
    965 
    966    NotNull<nsCOMPtr<nsIFile>> mDirectory;
    967    nsString mLeafName;
    968    nsCString mSpec;
    969    OriginAttributes mAttrs;
    970    int64_t mTimestamp;
    971    OriginMetadata mOriginMetadata;
    972    nsCString mOriginalSuffix;
    973 
    974    LazyInitializedOnceEarlyDestructible<const PersistenceType>
    975        mPersistenceType;
    976    Type mType;
    977    bool mNeedsRestore;
    978    bool mNeedsRestore2;
    979    bool mIgnore;
    980 
    981   public:
    982    explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
    983        : mDirectory(std::move(aDirectory)),
    984          mTimestamp(0),
    985          mType(eContent),
    986          mNeedsRestore(false),
    987          mNeedsRestore2(false),
    988          mIgnore(false) {}
    989 
    990    template <typename PersistenceTypeFunc>
    991    nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
    992  };
    993 
    994  nsTArray<OriginProps> mOriginProps;
    995 
    996  nsCOMPtr<nsIFile> mDirectory;
    997 
    998 public:
    999  explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
   1000    AssertIsOnIOThread();
   1001  }
   1002 
   1003  NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
   1004 
   1005 protected:
   1006  virtual ~StorageOperationBase() = default;
   1007 
   1008  static nsresult CreateDirectoryMetadata(
   1009      nsIFile& aDirectory, int64_t aTimestamp,
   1010      const OriginMetadata& aOriginMetadata);
   1011 
   1012  static nsresult CreateDirectoryMetadata2(
   1013      nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted,
   1014      const OriginMetadata& aOriginMetadata);
   1015 
   1016  nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
   1017                                nsACString& aGroup, nsACString& aOrigin,
   1018                                Nullable<bool>& aIsApp);
   1019 
   1020  // Upgrade helper to load the contents of ".metadata-v2" files from previous
   1021  // schema versions.  Although QuotaManager has a similar GetDirectoryMetadata2
   1022  // method, it is only intended to read current version ".metadata-v2" files.
   1023  // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
   1024  // because our "storage.sqlite" lets us track the overall version of the
   1025  // storage directory.
   1026  nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
   1027                                 nsACString& aSuffix, nsACString& aGroup,
   1028                                 nsACString& aOrigin, bool& aIsApp);
   1029 
   1030  int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);
   1031 
   1032  nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
   1033 
   1034  /**
   1035   * Rename the origin if the origin string generation from nsIPrincipal
   1036   * changed. This consists of renaming the origin in the metadata files and
   1037   * renaming the origin directory itself. For simplicity, the origin in
   1038   * metadata files is not actually updated, but the metadata files are
   1039   * recreated instead.
   1040   *
   1041   * @param  aOriginProps the properties of the origin to check.
   1042   *
   1043   * @return whether origin was renamed.
   1044   */
   1045  Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);
   1046 
   1047  nsresult ProcessOriginDirectories();
   1048 
   1049  virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
   1050 };
   1051 
   1052 class RepositoryOperationBase : public StorageOperationBase {
   1053 public:
   1054  explicit RepositoryOperationBase(nsIFile* aDirectory)
   1055      : StorageOperationBase(aDirectory) {}
   1056 
   1057  nsresult ProcessRepository();
   1058 
   1059 protected:
   1060  virtual ~RepositoryOperationBase() = default;
   1061 
   1062  template <typename UpgradeMethod>
   1063  nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
   1064                               UpgradeMethod aMethod);
   1065 
   1066 private:
   1067  virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;
   1068 
   1069  virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
   1070                                          bool* aRemoved) = 0;
   1071 
   1072  virtual nsresult PrepareClientDirectory(nsIFile* aFile,
   1073                                          const nsAString& aLeafName,
   1074                                          bool& aRemoved);
   1075 };
   1076 
   1077 class CreateOrUpgradeDirectoryMetadataHelper final
   1078    : public RepositoryOperationBase {
   1079  nsCOMPtr<nsIFile> mPermanentStorageDir;
   1080 
   1081  // The legacy PersistenceType, before the default repository introduction.
   1082  enum class LegacyPersistenceType {
   1083    Persistent = 0,
   1084    Temporary
   1085    // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
   1086    // it here.
   1087  };
   1088 
   1089  LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;
   1090 
   1091 public:
   1092  explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory)
   1093      : RepositoryOperationBase(aDirectory) {}
   1094 
   1095  nsresult Init();
   1096 
   1097 private:
   1098  Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile,
   1099                                                             const fallible_t&);
   1100 
   1101  PersistenceType PersistenceTypeFromLegacyPersistentSpec(
   1102      const nsCString& aSpec);
   1103 
   1104  PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
   1105 
   1106  nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
   1107 
   1108  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
   1109                                  bool* aRemoved) override;
   1110 
   1111  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
   1112 };
   1113 
   1114 class UpgradeStorageHelperBase : public RepositoryOperationBase {
   1115  LazyInitializedOnce<const PersistenceType> mPersistenceType;
   1116 
   1117 public:
   1118  explicit UpgradeStorageHelperBase(nsIFile* aDirectory)
   1119      : RepositoryOperationBase(aDirectory) {}
   1120 
   1121  nsresult Init();
   1122 
   1123 private:
   1124  PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
   1125 };
   1126 
   1127 class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase {
   1128 public:
   1129  explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory)
   1130      : UpgradeStorageHelperBase(aDirectory) {}
   1131 
   1132 private:
   1133  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
   1134                                  bool* aRemoved) override;
   1135 
   1136  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
   1137 };
   1138 
   1139 class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase {
   1140 public:
   1141  explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory)
   1142      : UpgradeStorageHelperBase(aDirectory) {}
   1143 
   1144 private:
   1145  nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);
   1146 
   1147  /**
   1148   * Remove the origin directory if appId is present in origin attributes.
   1149   *
   1150   * @param aOriginProps the properties of the origin to check.
   1151   *
   1152   * @return whether the origin directory was removed.
   1153   */
   1154  Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps);
   1155 
   1156  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
   1157                                  bool* aRemoved) override;
   1158 
   1159  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
   1160 };
   1161 
   1162 class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase {
   1163 public:
   1164  explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory)
   1165      : UpgradeStorageHelperBase(aDirectory) {}
   1166 
   1167 private:
   1168  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
   1169                                  bool* aRemoved) override;
   1170 
   1171  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
   1172 };
   1173 
   1174 class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase {
   1175 public:
   1176  explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory)
   1177      : UpgradeStorageHelperBase(aDirectory) {}
   1178 
   1179 private:
   1180  nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
   1181                                  bool* aRemoved) override;
   1182 
   1183  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
   1184 
   1185  nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName,
   1186                                  bool& aRemoved) override;
   1187 };
   1188 
   1189 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
   1190  LazyInitializedOnce<const PersistenceType> mPersistenceType;
   1191 
   1192 public:
   1193  explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory)
   1194      : StorageOperationBase(aDirectory) {}
   1195 
   1196  nsresult Init();
   1197 
   1198  nsresult RestoreMetadata2File();
   1199 
   1200 private:
   1201  nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
   1202 };
   1203 
   1204 Result<nsAutoString, nsresult> GetPathForStorage(
   1205    nsIFile& aBaseDir, const nsAString& aStorageName) {
   1206  QM_TRY_INSPECT(const auto& storageDir,
   1207                 CloneFileAndAppend(aBaseDir, aStorageName));
   1208 
   1209  QM_TRY_RETURN(
   1210      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, storageDir, GetPath));
   1211 }
   1212 
   1213 int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) {
   1214  AssertIsOnIOThread();
   1215 
   1216  class MOZ_STACK_CLASS Helper final {
   1217   public:
   1218    static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
   1219      AssertIsOnIOThread();
   1220      MOZ_ASSERT(aFile);
   1221      MOZ_ASSERT(aTimestamp);
   1222 
   1223      QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile));
   1224 
   1225      switch (dirEntryKind) {
   1226        case nsIFileKind::ExistsAsDirectory:
   1227          QM_TRY(CollectEachFile(
   1228              *aFile,
   1229              [&aTimestamp](const nsCOMPtr<nsIFile>& file)
   1230                  -> Result<mozilla::Ok, nsresult> {
   1231                QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file, aTimestamp)));
   1232 
   1233                return Ok{};
   1234              }));
   1235          break;
   1236 
   1237        case nsIFileKind::ExistsAsFile: {
   1238          QM_TRY_INSPECT(const auto& leafName,
   1239                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aFile,
   1240                                                           GetLeafName));
   1241 
   1242          // Bug 1595445 will handle unknown files here.
   1243 
   1244          if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
   1245              IsDotFile(leafName)) {
   1246            return NS_OK;
   1247          }
   1248 
   1249          QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
   1250                                               aFile, GetLastModifiedTime));
   1251 
   1252          // Need to convert from milliseconds to microseconds.
   1253          MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
   1254          timestamp *= int64_t(PR_USEC_PER_MSEC);
   1255 
   1256          if (timestamp > *aTimestamp) {
   1257            *aTimestamp = timestamp;
   1258          }
   1259          break;
   1260        }
   1261 
   1262        case nsIFileKind::DoesNotExist:
   1263          // Ignore files that got removed externally while iterating.
   1264          break;
   1265      }
   1266 
   1267      return NS_OK;
   1268    }
   1269  };
   1270 
   1271  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
   1272    return PR_Now();
   1273  }
   1274 
   1275  int64_t timestamp = INT64_MIN;
   1276  nsresult rv = Helper::GetLastModifiedTime(&aFile, &timestamp);
   1277  if (NS_FAILED(rv)) {
   1278    timestamp = PR_Now();
   1279  }
   1280 
   1281  // XXX if there were no suitable files for getting last modified time
   1282  // (timestamp is still set to INT64_MIN), we should return the current time
   1283  // instead of returning INT64_MIN.
   1284 
   1285  return timestamp;
   1286 }
   1287 
   1288 // Returns a bool indicating whether the directory was newly created.
   1289 Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) {
   1290  AssertIsOnIOThread();
   1291 
   1292  // Callers call this function without checking if the directory already
   1293  // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
   1294  // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
   1295  // reports.
   1296  QM_TRY_INSPECT(const auto& exists,
   1297                 QM_OR_ELSE_LOG_VERBOSE_IF(
   1298                     // Expression.
   1299                     MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Create,
   1300                                                 nsIFile::DIRECTORY_TYPE, 0755,
   1301                                                 /* aSkipAncestors = */ false)
   1302                         .map([](Ok) { return false; }),
   1303                     // Predicate.
   1304                     IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
   1305                     // Fallback.
   1306                     ErrToOk<true>));
   1307 
   1308  if (exists) {
   1309    QM_TRY_INSPECT(const bool& isDirectory,
   1310                   MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
   1311    QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
   1312  }
   1313 
   1314  return !exists;
   1315 }
   1316 
   1317 void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
   1318  aJarPrefix.Truncate();
   1319 
   1320  // Fallback.
   1321  if (!aInIsolatedMozBrowser) {
   1322    return;
   1323  }
   1324 
   1325  // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
   1326  // 1320404).
   1327  // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
   1328  aJarPrefix.AppendInt(0);  // TODO: this is the appId, to be removed.
   1329  aJarPrefix.Append('+');
   1330  aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
   1331  aJarPrefix.Append('+');
   1332 }
   1333 
   1334 // This method computes and returns our best guess for the temporary storage
   1335 // limit (in bytes), based on disk capacity.
   1336 Result<uint64_t, nsresult> GetTemporaryStorageLimit(nsIFile& aStorageDir) {
   1337  if (nsContentUtils::ShouldResistFingerprinting(
   1338          "The storage limit is set only once and not webpage specific.",
   1339          RFPTarget::DiskStorageLimit)) {
   1340    return nsRFPService::GetSpoofedStorageLimit();
   1341  }
   1342 
   1343  // The fixed limit pref can be used to override temporary storage limit
   1344  // calculation.
   1345  if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
   1346    return static_cast<uint64_t>(
   1347               StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
   1348           1024;
   1349  }
   1350 
   1351  constexpr int64_t teraByte = (1024LL * 1024LL * 1024LL * 1024LL);
   1352  constexpr int64_t maxAllowedCapacity = 8LL * teraByte;
   1353 
   1354  // Check for disk capacity of user's device on which storage directory lives.
   1355  int64_t diskCapacity = maxAllowedCapacity;
   1356 
   1357  // Log error when default disk capacity is returned due to the error
   1358  QM_WARNONLY_TRY(MOZ_TO_RESULT(aStorageDir.GetDiskCapacity(&diskCapacity)));
   1359 
   1360  MOZ_ASSERT(diskCapacity >= 0LL);
   1361 
   1362  // Allow temporary storage to consume up to 50% of disk capacity.
   1363  int64_t capacityLimit = diskCapacity / 2LL;
   1364 
   1365  // If the disk capacity reported by the operating system is very
   1366  // large and potentially incorrect due to hardware issues,
   1367  // a hardcoded limit is supplied instead.
   1368  QM_WARNONLY_TRY(
   1369      OkIf(capacityLimit < maxAllowedCapacity),
   1370      ([&capacityLimit](const auto&) { capacityLimit = maxAllowedCapacity; }));
   1371 
   1372  return capacityLimit;
   1373 }
   1374 
   1375 bool IsOriginUnaccessed(const FullOriginMetadata& aFullOriginMetadata,
   1376                        const int64_t aRecentTime) {
   1377  if (aFullOriginMetadata.mLastAccessTime > aRecentTime) {
   1378    return false;
   1379  }
   1380 
   1381  return (aRecentTime - aFullOriginMetadata.mLastAccessTime) / PR_USEC_PER_SEC >
   1382         StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec();
   1383 }
   1384 
   1385 }  // namespace
   1386 
   1387 /*******************************************************************************
   1388 * Exported functions
   1389 ******************************************************************************/
   1390 
   1391 void InitializeQuotaManager() {
   1392  MOZ_ASSERT(XRE_IsParentProcess());
   1393  MOZ_ASSERT(NS_IsMainThread());
   1394  MOZ_ASSERT(!gQuotaManagerInitialized);
   1395 
   1396  if (!QuotaManager::IsRunningGTests()) {
   1397    // These services have to be started on the main thread currently.
   1398    const nsCOMPtr<mozIStorageService> ss =
   1399        do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   1400    QM_WARNONLY_TRY(OkIf(ss));
   1401 
   1402    RefPtr<net::ExtensionProtocolHandler> extensionProtocolHandler =
   1403        net::ExtensionProtocolHandler::GetSingleton();
   1404    QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler));
   1405 
   1406    IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
   1407    QM_WARNONLY_TRY(MOZ_TO_RESULT(mgr));
   1408  }
   1409 
   1410  QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));
   1411 
   1412 #ifdef DEBUG
   1413  gQuotaManagerInitialized = true;
   1414 #endif
   1415 }
   1416 
   1417 void InitializeScopedLogExtraInfo() {
   1418 #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
   1419  ScopedLogExtraInfo::Initialize();
   1420 #endif
   1421 }
   1422 
   1423 bool RecvShutdownQuotaManager() {
   1424  AssertIsOnBackgroundThread();
   1425 
   1426  // If we are already in shutdown, don't call ShutdownInstance()
   1427  // again and return true immediately. We shall see this incident
   1428  // in Telemetry.
   1429  // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124)
   1430  // XXX todo: Active QM_TRY context for shutdown (Bug 1735170)
   1431  QM_TRY(OkIf(!gShutdown), true);
   1432 
   1433  QuotaManager::ShutdownInstance();
   1434 
   1435  return true;
   1436 }
   1437 
   1438 QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;
   1439 
   1440 // static
   1441 nsresult QuotaManager::Observer::Initialize() {
   1442  MOZ_ASSERT(NS_IsMainThread());
   1443 
   1444  RefPtr<Observer> observer = new Observer();
   1445 
   1446  nsresult rv = observer->Init();
   1447  if (NS_WARN_IF(NS_FAILED(rv))) {
   1448    return rv;
   1449  }
   1450 
   1451  sInstance = observer;
   1452 
   1453  return NS_OK;
   1454 }
   1455 
   1456 // static
   1457 nsIObserver* QuotaManager::Observer::GetInstance() {
   1458  MOZ_ASSERT(NS_IsMainThread());
   1459 
   1460  return sInstance;
   1461 }
   1462 
   1463 // static
   1464 void QuotaManager::Observer::ShutdownCompleted() {
   1465  MOZ_ASSERT(NS_IsMainThread());
   1466  MOZ_ASSERT(sInstance);
   1467 
   1468  sInstance->mShutdownComplete = true;
   1469 }
   1470 
   1471 nsresult QuotaManager::Observer::Init() {
   1472  MOZ_ASSERT(NS_IsMainThread());
   1473 
   1474  /**
   1475   * A RAII utility class to manage the registration and automatic
   1476   * unregistration of observers with `nsIObserverService`. This class is
   1477   * designed to simplify observer management, particularly when registering
   1478   * for multiple topics, by ensuring that already registered topics are
   1479   * unregistered if a failure occurs during subsequent registrations.
   1480   */
   1481  class MOZ_RAII Registrar {
   1482   public:
   1483    Registrar(nsIObserverService* aObserverService, nsIObserver* aObserver,
   1484              const char* aTopic)
   1485        : mObserverService(std::move(aObserverService)),
   1486          mObserver(aObserver),
   1487          mTopic(aTopic),
   1488          mUnregisterOnDestruction(false) {
   1489      MOZ_ASSERT(aObserverService);
   1490      MOZ_ASSERT(aObserver);
   1491      MOZ_ASSERT(aTopic);
   1492    }
   1493 
   1494    ~Registrar() {
   1495      if (mUnregisterOnDestruction) {
   1496        mObserverService->RemoveObserver(mObserver, mTopic);
   1497      }
   1498    }
   1499 
   1500    nsresult Register() {
   1501      MOZ_ASSERT(!mUnregisterOnDestruction);
   1502 
   1503      nsresult rv = mObserverService->AddObserver(mObserver, mTopic, false);
   1504      if (NS_WARN_IF(NS_FAILED(rv))) {
   1505        return rv;
   1506      }
   1507 
   1508      mUnregisterOnDestruction = true;
   1509 
   1510      return NS_OK;
   1511    }
   1512 
   1513    void Commit() { mUnregisterOnDestruction = false; }
   1514 
   1515   private:
   1516    nsIObserverService* mObserverService;
   1517    nsIObserver* mObserver;
   1518    const char* mTopic;
   1519    bool mUnregisterOnDestruction;
   1520  };
   1521 
   1522  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   1523  if (NS_WARN_IF(!obs)) {
   1524    return NS_ERROR_FAILURE;
   1525  }
   1526 
   1527  Registrar xpcomShutdownRegistrar(obs, this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
   1528  QM_TRY(MOZ_TO_RESULT(xpcomShutdownRegistrar.Register()));
   1529 
   1530  Registrar profileDoChangeRegistrar(obs, this, kProfileDoChangeTopic);
   1531  QM_TRY(MOZ_TO_RESULT(profileDoChangeRegistrar.Register()));
   1532 
   1533  Registrar contextualIdentityServiceLoadFinishedRegistrar(
   1534      obs, this, kContextualIdentityServiceLoadFinishedTopic);
   1535  QM_TRY(
   1536      MOZ_TO_RESULT(contextualIdentityServiceLoadFinishedRegistrar.Register()));
   1537 
   1538  Registrar profileBeforeChangeQmRegistrar(
   1539      obs, this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
   1540  QM_TRY(MOZ_TO_RESULT(profileBeforeChangeQmRegistrar.Register()));
   1541 
   1542  Registrar wakeNotificationRegistrar(obs, this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
   1543  QM_TRY(MOZ_TO_RESULT(wakeNotificationRegistrar.Register()));
   1544 
   1545  Registrar lastPbContextExitedRegistrar(obs, this,
   1546                                         kPrivateBrowsingObserverTopic);
   1547  QM_TRY(MOZ_TO_RESULT(lastPbContextExitedRegistrar.Register()));
   1548 
   1549  xpcomShutdownRegistrar.Commit();
   1550  profileDoChangeRegistrar.Commit();
   1551  contextualIdentityServiceLoadFinishedRegistrar.Commit();
   1552  profileBeforeChangeQmRegistrar.Commit();
   1553  wakeNotificationRegistrar.Commit();
   1554  lastPbContextExitedRegistrar.Commit();
   1555 
   1556  return NS_OK;
   1557 }
   1558 
   1559 nsresult QuotaManager::Observer::Shutdown() {
   1560  MOZ_ASSERT(NS_IsMainThread());
   1561 
   1562  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   1563  if (NS_WARN_IF(!obs)) {
   1564    return NS_ERROR_FAILURE;
   1565  }
   1566 
   1567  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kPrivateBrowsingObserverTopic));
   1568  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC));
   1569  MOZ_ALWAYS_SUCCEEDS(
   1570      obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
   1571  MOZ_ALWAYS_SUCCEEDS(
   1572      obs->RemoveObserver(this, kContextualIdentityServiceLoadFinishedTopic));
   1573  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
   1574  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
   1575 
   1576  sInstance = nullptr;
   1577 
   1578  // In general, the instance will have died after the latter removal call, so
   1579  // it's not safe to do anything after that point.
   1580  // However, Shutdown is currently called from Observe which is called by the
   1581  // Observer Service which holds a strong reference to the observer while the
   1582  // Observe method is being called.
   1583 
   1584  return NS_OK;
   1585 }
   1586 
   1587 NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)
   1588 
   1589 NS_IMETHODIMP
   1590 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
   1591                                const char16_t* aData) {
   1592  MOZ_ASSERT(NS_IsMainThread());
   1593 
   1594  nsresult rv;
   1595 
   1596  if (!strcmp(aTopic, kProfileDoChangeTopic)) {
   1597    if (NS_WARN_IF(gBasePath)) {
   1598      NS_WARNING(
   1599          "profile-before-change-qm must precede repeated "
   1600          "profile-do-change!");
   1601      return NS_OK;
   1602    }
   1603 
   1604    gBasePath = new nsString();
   1605 
   1606    nsCOMPtr<nsIFile> baseDir;
   1607    rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
   1608                                getter_AddRefs(baseDir));
   1609    if (NS_FAILED(rv)) {
   1610      rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
   1611                                  getter_AddRefs(baseDir));
   1612    }
   1613    if (NS_WARN_IF(NS_FAILED(rv))) {
   1614      return rv;
   1615    }
   1616 
   1617    rv = baseDir->GetPath(*gBasePath);
   1618    if (NS_WARN_IF(NS_FAILED(rv))) {
   1619      return rv;
   1620    }
   1621 
   1622 #ifdef XP_WIN
   1623    // Annotate if our profile lives on a network resource.
   1624    bool isNetworkPath = PathIsNetworkPathW(gBasePath->get());
   1625    CrashReporter::RecordAnnotationBool(
   1626        CrashReporter::Annotation::QuotaManagerStorageIsNetworkResource,
   1627        isNetworkPath);
   1628 #endif
   1629 
   1630    QM_LOG(("Base path: %s", NS_ConvertUTF16toUTF8(*gBasePath).get()));
   1631 
   1632    gStorageName = new nsString();
   1633 
   1634    rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName);
   1635    if (NS_FAILED(rv)) {
   1636      *gStorageName = kStorageName;
   1637    }
   1638 
   1639    gBuildId = new nsCString();
   1640 
   1641    nsCOMPtr<nsIPlatformInfo> platformInfo =
   1642        do_GetService("@mozilla.org/xre/app-info;1");
   1643    if (NS_WARN_IF(!platformInfo)) {
   1644      return NS_ERROR_FAILURE;
   1645    }
   1646 
   1647    rv = platformInfo->GetPlatformBuildID(*gBuildId);
   1648    if (NS_WARN_IF(NS_FAILED(rv))) {
   1649      return rv;
   1650    }
   1651 
   1652    return NS_OK;
   1653  }
   1654 
   1655  if (!strcmp(aTopic, kContextualIdentityServiceLoadFinishedTopic)) {
   1656    if (NS_WARN_IF(!gBasePath)) {
   1657      NS_WARNING(
   1658          "profile-do-change must precede "
   1659          "contextual-identity-service-load-finished!");
   1660      return NS_OK;
   1661    }
   1662 
   1663    nsCOMPtr<nsIQuotaManagerServiceInternal> quotaManagerService =
   1664        QuotaManagerService::GetOrCreate();
   1665    if (NS_WARN_IF(!quotaManagerService)) {
   1666      return NS_ERROR_FAILURE;
   1667    }
   1668 
   1669    nsCOMPtr<nsIQuotaUtilsService> quotaUtilsService =
   1670        do_GetService("@mozilla.org/dom/quota-utils-service;1");
   1671    if (NS_WARN_IF(!quotaUtilsService)) {
   1672      return NS_ERROR_FAILURE;
   1673    }
   1674 
   1675    uint32_t thumbnailPrivateIdentityId;
   1676    nsresult rv = quotaUtilsService->GetPrivateIdentityId(
   1677        u"userContextIdInternal.thumbnail"_ns, &thumbnailPrivateIdentityId);
   1678    if (NS_WARN_IF(NS_FAILED(rv))) {
   1679      return rv;
   1680    }
   1681 
   1682    rv = quotaManagerService->SetThumbnailPrivateIdentityId(
   1683        thumbnailPrivateIdentityId);
   1684    if (NS_WARN_IF(NS_FAILED(rv))) {
   1685      return rv;
   1686    }
   1687 
   1688    return NS_OK;
   1689  }
   1690 
   1691  if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
   1692    if (NS_WARN_IF(!gBasePath)) {
   1693      NS_WARNING("profile-do-change must precede profile-before-change-qm!");
   1694      return NS_OK;
   1695    }
   1696 
   1697    // mPendingProfileChange is our re-entrancy guard (the nested event loop
   1698    // below may cause re-entrancy).
   1699    if (mPendingProfileChange) {
   1700      return NS_OK;
   1701    }
   1702 
   1703    AutoRestore<bool> pending(mPendingProfileChange);
   1704    mPendingProfileChange = true;
   1705 
   1706    mShutdownComplete = false;
   1707 
   1708    PBackgroundChild* backgroundActor =
   1709        BackgroundChild::GetOrCreateForCurrentThread();
   1710    if (NS_WARN_IF(!backgroundActor)) {
   1711      return NS_ERROR_FAILURE;
   1712    }
   1713 
   1714    if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
   1715      return NS_ERROR_FAILURE;
   1716    }
   1717 
   1718    MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
   1719        "QuotaManager::Observer::Observe profile-before-change-qm"_ns,
   1720        [&]() { return mShutdownComplete; }));
   1721 
   1722    gBasePath = nullptr;
   1723 
   1724    gStorageName = nullptr;
   1725 
   1726    gBuildId = nullptr;
   1727 
   1728    return NS_OK;
   1729  }
   1730 
   1731  if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) {
   1732    auto* const quotaManagerService = QuotaManagerService::GetOrCreate();
   1733    if (NS_WARN_IF(!quotaManagerService)) {
   1734      return NS_ERROR_FAILURE;
   1735    }
   1736 
   1737    nsCOMPtr<nsIQuotaRequest> request;
   1738    rv = quotaManagerService->ClearStoragesForPrivateBrowsing(
   1739        nsGetterAddRefs(request));
   1740    if (NS_WARN_IF(NS_FAILED(rv))) {
   1741      return rv;
   1742    }
   1743 
   1744    return NS_OK;
   1745  }
   1746 
   1747  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
   1748    rv = Shutdown();
   1749    if (NS_WARN_IF(NS_FAILED(rv))) {
   1750      return rv;
   1751    }
   1752 
   1753    return NS_OK;
   1754  }
   1755 
   1756  if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
   1757    gLastOSWake = TimeStamp::Now();
   1758 
   1759    return NS_OK;
   1760  }
   1761 
   1762  NS_WARNING("Unknown observer topic!");
   1763  return NS_OK;
   1764 }
   1765 
   1766 /*******************************************************************************
   1767 * Quota manager
   1768 ******************************************************************************/
   1769 
   1770 QuotaManager::QuotaManager(const nsAString& aBasePath,
   1771                           const nsAString& aStorageName)
   1772    : mQuotaMutex("QuotaManager.mQuotaMutex"),
   1773      mBasePath(aBasePath),
   1774      mStorageName(aStorageName),
   1775      mTemporaryStorageUsage(0),
   1776      mNextDirectoryLockId(0),
   1777      mStorageInitialized(false),
   1778      mPersistentStorageInitialized(false),
   1779      mPersistentStorageInitializedInternal(false),
   1780      mTemporaryStorageInitialized(false),
   1781      mTemporaryStorageInitializedInternal(false),
   1782      mInitializingAllTemporaryOrigins(false),
   1783      mAllTemporaryOriginsInitialized(false),
   1784      mCacheUsable(false) {
   1785  AssertIsOnOwningThread();
   1786  MOZ_ASSERT(!gInstance);
   1787 }
   1788 
   1789 QuotaManager::~QuotaManager() {
   1790  AssertIsOnOwningThread();
   1791  MOZ_ASSERT(!gInstance || gInstance == this);
   1792 }
   1793 
   1794 // static
   1795 nsresult QuotaManager::Initialize() {
   1796  MOZ_ASSERT(NS_IsMainThread());
   1797 
   1798  nsresult rv = Observer::Initialize();
   1799  if (NS_WARN_IF(NS_FAILED(rv))) {
   1800    return rv;
   1801  }
   1802 
   1803  return NS_OK;
   1804 }
   1805 
   1806 // static
   1807 Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult>
   1808 QuotaManager::GetOrCreate() {
   1809  AssertIsOnBackgroundThread();
   1810 
   1811  if (gInstance) {
   1812    return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance});
   1813  }
   1814 
   1815  QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) {
   1816    NS_WARNING(
   1817        "Trying to create QuotaManager before profile-do-change! "
   1818        "Forgot to call do_get_profile()?");
   1819  });
   1820 
   1821  QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) {
   1822    MOZ_ASSERT(false,
   1823               "Trying to create QuotaManager after profile-before-change-qm!");
   1824  });
   1825 
   1826  auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName);
   1827 
   1828  QM_TRY(MOZ_TO_RESULT(instance->Init()));
   1829 
   1830  gInstance = instance;
   1831 
   1832  // Do this before clients have a chance to acquire a directory lock for the
   1833  // private repository.
   1834  gInstance->ClearPrivateRepository();
   1835 
   1836  return WrapMovingNotNullUnchecked(std::move(instance));
   1837 }
   1838 
   1839 Result<Ok, nsresult> QuotaManager::EnsureCreated() {
   1840  AssertIsOnBackgroundThread();
   1841 
   1842  QM_TRY_RETURN(GetOrCreate().map([](const auto& res) { return Ok{}; }))
   1843 }
   1844 
   1845 // static
   1846 QuotaManager* QuotaManager::Get() {
   1847  // Does not return an owning reference.
   1848  return gInstance;
   1849 }
   1850 
   1851 // static
   1852 nsIObserver* QuotaManager::GetObserver() {
   1853  MOZ_ASSERT(NS_IsMainThread());
   1854 
   1855  return Observer::GetInstance();
   1856 }
   1857 
   1858 // static
   1859 void QuotaManager::ProcessPendingNormalOriginOperations() {
   1860  MOZ_ASSERT(IsRunningGTests());
   1861 
   1862  // Processes any pending events that may create normal origin operations.
   1863  // This is needed in cases where an async method (e.g., InitializeStorage)
   1864  // is called without a pre-acquired directory lock, which causes the
   1865  // operation to be created and scheduled after the directory lock is
   1866  // acquired.
   1867  NS_ProcessPendingEvents(nullptr);
   1868 
   1869  // Wait until all normal origin operations have completed.
   1870  MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
   1871      "QuotaManager::ProcessPendingNormalOriginOperations"_ns,
   1872      []() { return !gNormalOriginOps; }));
   1873 
   1874  // Once an operation completes, it is removed from gNormalOriginOps. However,
   1875  // there may still be a follow-up event pending that updates a flag after the
   1876  // operation has finished. We need to process that event as well; otherwise,
   1877  // callers of this helper may see inconsistent state.
   1878  // For example, IsStorageInitialized could still return false even after
   1879  // calling InitializeStorage and ProcessPendingNormalOriginOperations.
   1880  NS_ProcessPendingEvents(nullptr);
   1881 }
   1882 
   1883 // static
   1884 bool QuotaManager::IsShuttingDown() { return gShutdown; }
   1885 
   1886 // static
   1887 void QuotaManager::ShutdownInstance() {
   1888  AssertIsOnBackgroundThread();
   1889 
   1890  if (gInstance) {
   1891    auto recordTimeDeltaHelper =
   1892        MakeRefPtr<RecordTimeDeltaHelper>(glean::dom_quota::shutdown_time);
   1893 
   1894    recordTimeDeltaHelper->Start();
   1895 
   1896    // Glean SDK recommends using its own timing APIs where possible. In this
   1897    // case, we use NowExcludingSuspendMs() directly to manually calculate a
   1898    // duration that excludes suspend time. This is a valid exception because
   1899    // our use case is sensitive to suspend, and we need full control over the
   1900    // timing logic.
   1901    //
   1902    // We are currently recording both this and the older helper-based
   1903    // measurement. The results are not directly comparable, since the new API
   1904    // uses monotonic time. If this approach proves more reliable, we'll retire
   1905    // the old telemetry, change the expiration of the new metric to never,
   1906    // and add a matching "including suspend" version.
   1907 
   1908    const auto startExcludingSuspendMs = NowExcludingSuspendMs();
   1909 
   1910    gInstance->Shutdown();
   1911 
   1912    const auto endExcludingSuspendMs = NowExcludingSuspendMs();
   1913 
   1914    if (startExcludingSuspendMs && endExcludingSuspendMs) {
   1915      const auto duration = TimeDuration::FromMilliseconds(
   1916          *endExcludingSuspendMs - *startExcludingSuspendMs);
   1917 
   1918      glean::quotamanager_shutdown::total_time_excluding_suspend
   1919          .AccumulateRawDuration(duration);
   1920    }
   1921 
   1922    recordTimeDeltaHelper->End();
   1923 
   1924    gInstance = nullptr;
   1925  } else {
   1926    // If we were never initialized, just set the flag to avoid late creation.
   1927    gShutdown = true;
   1928  }
   1929 
   1930  RefPtr<Runnable> runnable =
   1931      NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
   1932                             []() { Observer::ShutdownCompleted(); });
   1933  MOZ_ASSERT(runnable);
   1934 
   1935  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
   1936 }
   1937 
   1938 // static
   1939 void QuotaManager::Reset() {
   1940  AssertIsOnBackgroundThread();
   1941  MOZ_ASSERT(!gInstance);
   1942  MOZ_ASSERT(gShutdown);
   1943 
   1944  gShutdown = false;
   1945 }
   1946 
   1947 // static
   1948 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
   1949  return mozilla::dom::quota::IsOSMetadata(aFileName);
   1950 }
   1951 
   1952 // static
   1953 bool QuotaManager::IsDotFile(const nsAString& aFileName) {
   1954  return mozilla::dom::quota::IsDotFile(aFileName);
   1955 }
   1956 
   1957 void QuotaManager::RegisterNormalOriginOp(
   1958    NormalOriginOperationBase& aNormalOriginOp) {
   1959  AssertIsOnBackgroundThread();
   1960 
   1961  if (!gNormalOriginOps) {
   1962    gNormalOriginOps = new NormalOriginOpArray();
   1963  }
   1964 
   1965  gNormalOriginOps->AppendElement(&aNormalOriginOp);
   1966 }
   1967 
   1968 void QuotaManager::UnregisterNormalOriginOp(
   1969    NormalOriginOperationBase& aNormalOriginOp) {
   1970  AssertIsOnBackgroundThread();
   1971  MOZ_ASSERT(gNormalOriginOps);
   1972 
   1973  gNormalOriginOps->RemoveElement(&aNormalOriginOp);
   1974 
   1975  if (gNormalOriginOps->IsEmpty()) {
   1976    gNormalOriginOps = nullptr;
   1977  }
   1978 }
   1979 
   1980 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) {
   1981  AssertIsOnOwningThread();
   1982 
   1983  mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock));
   1984 
   1985  if (aLock.mExclusive) {
   1986    mExclusiveDirectoryLocks.AppendElement(WrapNotNull(&aLock));
   1987  }
   1988 
   1989  if (aLock.ShouldUpdateLockIdTable()) {
   1990    MutexAutoLock lock(mQuotaMutex);
   1991 
   1992    MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id()));
   1993    mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(),
   1994                                         WrapNotNullUnchecked(&aLock));
   1995  }
   1996 
   1997  aLock.SetRegistered(true);
   1998 }
   1999 
   2000 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) {
   2001  AssertIsOnOwningThread();
   2002 
   2003  MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock));
   2004 
   2005  if (aLock.mExclusive) {
   2006    MOZ_ALWAYS_TRUE(mExclusiveDirectoryLocks.RemoveElement(&aLock));
   2007  }
   2008 
   2009  if (aLock.ShouldUpdateLockIdTable()) {
   2010    MutexAutoLock lock(mQuotaMutex);
   2011 
   2012    MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id()));
   2013    mDirectoryLockIdTable.Remove(aLock.Id());
   2014  }
   2015 
   2016  aLock.SetRegistered(false);
   2017 }
   2018 
   2019 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) {
   2020  AssertIsOnOwningThread();
   2021 
   2022  mPendingDirectoryLocks.AppendElement(&aLock);
   2023 }
   2024 
   2025 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) {
   2026  AssertIsOnOwningThread();
   2027 
   2028  MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock));
   2029 }
   2030 
   2031 uint64_t QuotaManager::CollectOriginsForEviction(
   2032    uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
   2033  AssertIsOnOwningThread();
   2034  MOZ_ASSERT(aLocks.IsEmpty());
   2035 
   2036  // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
   2037  // or maybe a generalization if that.
   2038 
   2039  struct MOZ_STACK_CLASS Helper final {
   2040    static void GetInactiveOriginInfos(
   2041        const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos,
   2042        const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks,
   2043        OriginInfosFlatTraversable& aInactiveOriginInfos) {
   2044      for (const auto& originInfo : aOriginInfos) {
   2045        MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
   2046                   PERSISTENCE_TYPE_PERSISTENT);
   2047 
   2048        if (originInfo->LockedPersisted()) {
   2049          continue;
   2050        }
   2051 
   2052        // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a
   2053        // moz-extension origin, unlike websites (which may more likely using
   2054        // the local data as a cache but still able to retrieve the same data
   2055        // from the server side) extensions do not have the same data stored
   2056        // anywhere else and evicting the data would result into potential data
   2057        // loss for the users.
   2058        //
   2059        // Also, unlike a website the extensions are explicitly installed and
   2060        // uninstalled by the user and all data associated to the extension
   2061        // principal will be completely removed once the addon is uninstalled.
   2062        if (originInfo->mGroupInfo->mPersistenceType !=
   2063                PERSISTENCE_TYPE_TEMPORARY &&
   2064            originInfo->IsExtensionOrigin()) {
   2065          continue;
   2066        }
   2067 
   2068        const auto originScope =
   2069            OriginScope::FromOrigin(originInfo->FlattenToOriginMetadata());
   2070 
   2071        const bool match =
   2072            std::any_of(aLocks.begin(), aLocks.end(),
   2073                        [&originScope](const DirectoryLockImpl* const lock) {
   2074                          return originScope.Matches(lock->GetOriginScope());
   2075                        });
   2076 
   2077        if (!match) {
   2078          MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count(),
   2079                     "Inactive origin shouldn't have open files!");
   2080          aInactiveOriginInfos.InsertElementSorted(
   2081              originInfo, OriginInfoAccessTimeComparator());
   2082        }
   2083      }
   2084    }
   2085  };
   2086 
   2087  // Split locks into separate arrays and filter out locks for persistent
   2088  // storage, they can't block us.
   2089  auto [temporaryStorageLocks, defaultStorageLocks,
   2090        privateStorageLocks] = [this] {
   2091    nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks;
   2092    nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks;
   2093    nsTArray<NotNull<const DirectoryLockImpl*>> privateStorageLocks;
   2094 
   2095    for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) {
   2096      const PersistenceScope& persistenceScope = lock->PersistenceScopeRef();
   2097 
   2098      if (persistenceScope.Matches(
   2099              PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_TEMPORARY))) {
   2100        temporaryStorageLocks.AppendElement(lock);
   2101      }
   2102 
   2103      if (persistenceScope.Matches(
   2104              PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_DEFAULT))) {
   2105        defaultStorageLocks.AppendElement(lock);
   2106      }
   2107 
   2108      if (persistenceScope.Matches(
   2109              PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PRIVATE))) {
   2110        privateStorageLocks.AppendElement(lock);
   2111      }
   2112    }
   2113 
   2114    return std::make_tuple(std::move(temporaryStorageLocks),
   2115                           std::move(defaultStorageLocks),
   2116                           std::move(privateStorageLocks));
   2117  }();
   2118 
   2119  // Enumerate and process inactive origins. This must be protected by the
   2120  // mutex.
   2121  MutexAutoLock lock(mQuotaMutex);
   2122 
   2123  const auto [inactiveOrigins, sizeToBeFreed] =
   2124      [this, &temporaryStorageLocks = temporaryStorageLocks,
   2125       &defaultStorageLocks = defaultStorageLocks,
   2126       &privateStorageLocks = privateStorageLocks, aMinSizeToBeFreed] {
   2127        nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins;
   2128        for (const auto& entry : mGroupInfoPairs) {
   2129          const auto& pair = entry.GetData();
   2130 
   2131          MOZ_ASSERT(!entry.GetKey().IsEmpty());
   2132          MOZ_ASSERT(pair);
   2133 
   2134          RefPtr<GroupInfo> groupInfo =
   2135              pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
   2136          if (groupInfo) {
   2137            Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
   2138                                           temporaryStorageLocks,
   2139                                           inactiveOrigins);
   2140          }
   2141 
   2142          groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
   2143          if (groupInfo) {
   2144            Helper::GetInactiveOriginInfos(
   2145                groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins);
   2146          }
   2147 
   2148          groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
   2149          if (groupInfo) {
   2150            Helper::GetInactiveOriginInfos(
   2151                groupInfo->mOriginInfos, privateStorageLocks, inactiveOrigins);
   2152          }
   2153        }
   2154 
   2155 #ifdef DEBUG
   2156        // Make sure the array is sorted correctly.
   2157        const bool inactiveOriginsSorted =
   2158            std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(),
   2159                           [](const auto& lhs, const auto& rhs) {
   2160                             return lhs->mAccessTime < rhs->mAccessTime;
   2161                           });
   2162        MOZ_ASSERT(inactiveOriginsSorted);
   2163 #endif
   2164 
   2165        // Create a list of inactive and the least recently used origins
   2166        // whose aggregate size is greater or equals the minimal size to be
   2167        // freed.
   2168        uint64_t sizeToBeFreed = 0;
   2169        for (uint32_t count = inactiveOrigins.Length(), index = 0;
   2170             index < count; index++) {
   2171          if (sizeToBeFreed >= aMinSizeToBeFreed) {
   2172            inactiveOrigins.TruncateLength(index);
   2173            break;
   2174          }
   2175 
   2176          sizeToBeFreed += inactiveOrigins[index]->LockedUsage();
   2177        }
   2178 
   2179        return std::pair(std::move(inactiveOrigins), sizeToBeFreed);
   2180      }();
   2181 
   2182  if (sizeToBeFreed >= aMinSizeToBeFreed) {
   2183    // Success, add directory locks for these origins, so any other
   2184    // operations for them will be delayed (until origin eviction is finalized).
   2185 
   2186    for (const auto& originInfo : inactiveOrigins) {
   2187      auto lock = OriginDirectoryLock::CreateForEviction(
   2188          WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType,
   2189          originInfo->FlattenToOriginMetadata());
   2190 
   2191      lock->AcquireImmediately();
   2192 
   2193      aLocks.AppendElement(lock.forget());
   2194    }
   2195 
   2196    return sizeToBeFreed;
   2197  }
   2198 
   2199  return 0;
   2200 }
   2201 
   2202 nsresult QuotaManager::Init() {
   2203  AssertIsOnOwningThread();
   2204 
   2205 #ifdef XP_WIN
   2206  CacheUseDOSDevicePathSyntaxPrefValue();
   2207 #endif
   2208 
   2209  QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath));
   2210 
   2211  QM_TRY_UNWRAP(
   2212      do_Init(mIndexedDBPath),
   2213      GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME)));
   2214 
   2215  QM_TRY(MOZ_TO_RESULT(baseDir->Append(mStorageName)));
   2216 
   2217  QM_TRY_UNWRAP(do_Init(mStoragePath),
   2218                MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, baseDir, GetPath));
   2219 
   2220  QM_TRY_UNWRAP(
   2221      do_Init(mStorageArchivesPath),
   2222      GetPathForStorage(*baseDir, nsLiteralString(ARCHIVES_DIRECTORY_NAME)));
   2223 
   2224  QM_TRY_UNWRAP(
   2225      do_Init(mPermanentStoragePath),
   2226      GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME)));
   2227 
   2228  QM_TRY_UNWRAP(
   2229      do_Init(mTemporaryStoragePath),
   2230      GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
   2231 
   2232  QM_TRY_UNWRAP(
   2233      do_Init(mDefaultStoragePath),
   2234      GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
   2235 
   2236  QM_TRY_UNWRAP(
   2237      do_Init(mPrivateStoragePath),
   2238      GetPathForStorage(*baseDir, nsLiteralString(PRIVATE_DIRECTORY_NAME)));
   2239 
   2240  QM_TRY_UNWRAP(
   2241      do_Init(mToBeRemovedStoragePath),
   2242      GetPathForStorage(*baseDir, nsLiteralString(TOBEREMOVED_DIRECTORY_NAME)));
   2243 
   2244  QM_TRY_UNWRAP(do_Init(mIOThread),
   2245                MOZ_TO_RESULT_INVOKE_TYPED(
   2246                    nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
   2247                    "QuotaManager IO"));
   2248 
   2249  // XXX This could be eventually moved to nsThreadUtils.h or nsIThread
   2250  // could have an infallible method returning PRThread as return value.
   2251  auto PRThreadFromThread = [](nsIThread* aThread) {
   2252    MOZ_ASSERT(aThread);
   2253 
   2254    PRThread* result;
   2255    MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
   2256    MOZ_ASSERT(result);
   2257 
   2258    return result;
   2259  };
   2260 
   2261  mIOThreadAccessible.Transfer(PRThreadFromThread(*mIOThread));
   2262 
   2263  static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
   2264                    Client::FILESYSTEM == 3 && Client::LS == 4 &&
   2265                    Client::TYPE_MAX == 5,
   2266                "Fix the registration!");
   2267 
   2268  // Register clients.
   2269  auto clients = decltype(mClients)::ValueType{};
   2270  clients.AppendElement(indexedDB::CreateQuotaClient());
   2271  clients.AppendElement(cache::CreateQuotaClient());
   2272  clients.AppendElement(simpledb::CreateQuotaClient());
   2273  clients.AppendElement(fs::CreateQuotaClient());
   2274  if (NextGenLocalStorageEnabled()) {
   2275    clients.AppendElement(localstorage::CreateQuotaClient());
   2276  } else {
   2277    clients.SetLength(Client::TypeMax());
   2278  }
   2279 
   2280  mClients.init(std::move(clients));
   2281 
   2282  MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX,
   2283             "Should be using an auto array with correct capacity!");
   2284 
   2285  mAllClientTypes.init(ClientTypesArray{
   2286      Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB,
   2287      Client::Type::FILESYSTEM, Client::Type::LS});
   2288  mAllClientTypesExceptLS.init(
   2289      ClientTypesArray{Client::Type::IDB, Client::Type::DOMCACHE,
   2290                       Client::Type::SDB, Client::Type::FILESYSTEM});
   2291 
   2292  return NS_OK;
   2293 }
   2294 
   2295 // static
   2296 void QuotaManager::MaybeRecordQuotaClientShutdownStep(
   2297    const Client::Type aClientType, const nsACString& aStepDescription) {
   2298  // Callable on any thread.
   2299 
   2300  auto* const quotaManager = QuotaManager::Get();
   2301  MOZ_DIAGNOSTIC_ASSERT(quotaManager);
   2302 
   2303  if (quotaManager->IsShuttingDown()) {
   2304    quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
   2305  }
   2306 }
   2307 
   2308 // static
   2309 void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
   2310    const Client::Type aClientType, const nsACString& aStepDescription) {
   2311  // Callable on any thread.
   2312 
   2313  auto* const quotaManager = QuotaManager::Get();
   2314 
   2315  if (quotaManager && quotaManager->IsShuttingDown()) {
   2316    quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
   2317  }
   2318 }
   2319 
   2320 void QuotaManager::RecordQuotaManagerShutdownStep(
   2321    const nsACString& aStepDescription) {
   2322  // Callable on any thread.
   2323  MOZ_ASSERT(IsShuttingDown());
   2324 
   2325  RecordShutdownStep(Nothing{}, aStepDescription);
   2326 }
   2327 
   2328 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
   2329    const nsACString& aStepDescription) {
   2330  // Callable on any thread.
   2331 
   2332  if (IsShuttingDown()) {
   2333    RecordQuotaManagerShutdownStep(aStepDescription);
   2334  }
   2335 }
   2336 
   2337 void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType,
   2338                                      const nsACString& aStepDescription) {
   2339  MOZ_ASSERT(IsShuttingDown());
   2340 
   2341  const TimeDuration elapsedSinceShutdownStart =
   2342      TimeStamp::NowLoRes() - *mShutdownStartedAt;
   2343 
   2344  const auto stepString =
   2345      nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(),
   2346                      nsPromiseFlatCString(aStepDescription).get());
   2347 
   2348  if (aClientType) {
   2349    AssertIsOnBackgroundThread();
   2350 
   2351    mShutdownSteps[*aClientType].Append(stepString + "\n"_ns);
   2352  } else {
   2353    // Callable on any thread.
   2354    MutexAutoLock lock(mQuotaMutex);
   2355 
   2356    mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns);
   2357  }
   2358 
   2359 #ifdef DEBUG
   2360  // XXX Probably this isn't the mechanism that should be used here.
   2361 
   2362  NS_DebugBreak(
   2363      NS_DEBUG_WARNING,
   2364      nsAutoCString(aClientType ? Client::TypeToText(*aClientType)
   2365                                : "quota manager"_ns + " shutdown step"_ns)
   2366          .get(),
   2367      stepString.get(), __FILE__, __LINE__);
   2368 #endif
   2369 }
   2370 
   2371 void QuotaManager::Shutdown() {
   2372  AssertIsOnOwningThread();
   2373  MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
   2374 
   2375  // Define some local helper functions
   2376 
   2377  auto flagShutdownStarted = [this]() {
   2378    mShutdownStartedAt.init(TimeStamp::NowLoRes());
   2379 
   2380    // Setting this flag prevents the service from being recreated and prevents
   2381    // further storages from being created.
   2382    gShutdown = true;
   2383  };
   2384 
   2385  nsCOMPtr<nsITimer> crashBrowserTimer;
   2386 
   2387  auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) {
   2388    auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
   2389 
   2390    quotaManager->RecordQuotaManagerShutdownStep(
   2391        "crashBrowserTimerCallback"_ns);
   2392 
   2393    nsCString annotation;
   2394 
   2395    for (Client::Type type : quotaManager->AllClientTypes()) {
   2396      auto& quotaClient = *(*quotaManager->mClients)[type];
   2397 
   2398      if (!quotaClient.IsShutdownCompleted()) {
   2399        annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
   2400                                Client::TypeToText(type).get(),
   2401                                quotaClient.GetShutdownStatus().get(),
   2402                                quotaManager->mShutdownSteps[type].get());
   2403      }
   2404    }
   2405 
   2406    if (gNormalOriginOps) {
   2407      annotation.AppendPrintf("QM: %zu normal origin ops pending\n",
   2408                              gNormalOriginOps->Length());
   2409 
   2410      for (const auto& op : *gNormalOriginOps) {
   2411 #ifdef QM_COLLECTING_OPERATION_TELEMETRY
   2412        annotation.AppendPrintf("Op: %s pending\n", op->Name());
   2413 #endif
   2414 
   2415        nsCString data;
   2416        op->Stringify(data);
   2417 
   2418        annotation.AppendPrintf("Op details:\n%s\n", data.get());
   2419      }
   2420    }
   2421    {
   2422      MutexAutoLock lock(quotaManager->mQuotaMutex);
   2423 
   2424      annotation.AppendPrintf("Intermediate steps:\n%s\n",
   2425                              quotaManager->mQuotaManagerShutdownSteps.get());
   2426    }
   2427 
   2428    CrashReporter::RecordAnnotationNSCString(
   2429        CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation);
   2430 
   2431    MOZ_CRASH("Quota manager shutdown timed out");
   2432  };
   2433 
   2434  auto startCrashBrowserTimer = [&]() {
   2435    crashBrowserTimer = NS_NewTimer();
   2436    MOZ_ASSERT(crashBrowserTimer);
   2437    if (crashBrowserTimer) {
   2438      RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns);
   2439      MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback(
   2440          crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS,
   2441          nsITimer::TYPE_ONE_SHOT,
   2442          "quota::QuotaManager::Shutdown::crashBrowserTimer"_ns));
   2443    }
   2444  };
   2445 
   2446  auto stopCrashBrowserTimer = [&]() {
   2447    if (crashBrowserTimer) {
   2448      RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns);
   2449      QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel()));
   2450    }
   2451  };
   2452 
   2453  auto initiateShutdownWorkThreads = [this]() {
   2454    RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns);
   2455    bool needsToWait = false;
   2456    for (Client::Type type : AllClientTypes()) {
   2457      // Clients are supposed to also AbortAllOperations from this point on
   2458      // to speed up shutdown, if possible. Thus pending operations
   2459      // might not be executed anymore.
   2460      needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
   2461    }
   2462 
   2463    return needsToWait;
   2464  };
   2465 
   2466  nsCOMPtr<nsITimer> killActorsTimer;
   2467 
   2468  auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) {
   2469    auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
   2470 
   2471    quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns);
   2472 
   2473    // XXX: This abort is a workaround to unblock shutdown, which
   2474    // ought to be removed by bug 1682326. We probably need more
   2475    // checks to immediately abort new operations during
   2476    // shutdown.
   2477    quotaManager->GetClient(Client::IDB)->AbortAllOperations();
   2478 
   2479    for (Client::Type type : quotaManager->AllClientTypes()) {
   2480      quotaManager->GetClient(type)->ForceKillActors();
   2481    }
   2482  };
   2483 
   2484  auto startKillActorsTimer = [&]() {
   2485    killActorsTimer = NS_NewTimer();
   2486    MOZ_ASSERT(killActorsTimer);
   2487    if (killActorsTimer) {
   2488      RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns);
   2489      MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback(
   2490          killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
   2491          nsITimer::TYPE_ONE_SHOT,
   2492          "quota::QuotaManager::Shutdown::killActorsTimer"_ns));
   2493    }
   2494  };
   2495 
   2496  auto stopKillActorsTimer = [&]() {
   2497    if (killActorsTimer) {
   2498      RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns);
   2499      QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel()));
   2500    }
   2501  };
   2502 
   2503  auto isAllClientsShutdownComplete = [this] {
   2504    return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
   2505                       [&self = *this](const auto type) {
   2506                         return (*self.mClients)[type]->IsShutdownCompleted();
   2507                       });
   2508  };
   2509 
   2510  auto shutdownAndJoinWorkThreads = [this]() {
   2511    RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns);
   2512    for (Client::Type type : AllClientTypes()) {
   2513      (*mClients)[type]->FinalizeShutdownWorkThreads();
   2514    }
   2515  };
   2516 
   2517  auto shutdownAndJoinIOThread = [this]() {
   2518    RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns);
   2519 
   2520    // Make sure to join with our IO thread.
   2521    QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
   2522  };
   2523 
   2524  auto invalidatePendingDirectoryLocks = [this]() {
   2525    RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns);
   2526    for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
   2527      lock->Invalidate();
   2528    }
   2529  };
   2530 
   2531  // Body of the function
   2532 
   2533  ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContextTainted,
   2534                           "dom::quota::QuotaManager::Shutdown"_ns};
   2535  GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::Shutdown");
   2536 
   2537  // We always need to ensure that firefox does not shutdown with a private
   2538  // repository still on disk. They are ideally cleaned up on PBM session end
   2539  // but, in some cases like PBM autostart (i.e.
   2540  // browser.privatebrowsing.autostart), private repository could only be
   2541  // cleaned up on shutdown. ClearPrivateRepository below runs a async op and is
   2542  // better to do it before we run the ShutdownStorageOp since it expects all
   2543  // cleanup operations to be done by that point. We don't need to use the
   2544  // returned promise here because `ClearPrivateRepository` registers the
   2545  // underlying `ClearPrivateRepositoryOp` in `gNormalOriginOps`.
   2546  ClearPrivateRepository();
   2547 
   2548  // This must be called before `flagShutdownStarted`, it would fail otherwise.
   2549  // `ShutdownStorageOp` needs to acquire an exclusive directory lock over
   2550  // entire <profile>/storage which will abort any existing operations and wait
   2551  // for all existing directory locks to be released. So the shutdown operation
   2552  // will effectively run after all existing operations.
   2553  // Similar, to ClearPrivateRepository operation above, ShutdownStorageOp also
   2554  // registers it's operation in `gNormalOriginOps` so we don't need to assign
   2555  // returned promise.
   2556  ShutdownStorage();
   2557 
   2558  flagShutdownStarted();
   2559 
   2560  startCrashBrowserTimer();
   2561 
   2562  // XXX: StopIdleMaintenance now just notifies all clients to abort any
   2563  // maintenance work.
   2564  // This could be done as part of QuotaClient::AbortAllOperations.
   2565  StopIdleMaintenance();
   2566 
   2567  // XXX In theory, we could simplify the code below (and also the `Client`
   2568  // interface) by removing the `initiateShutdownWorkThreads` and
   2569  // `isAllClientsShutdownComplete` calls because it should be sufficient
   2570  // to rely on `ShutdownStorage` to abort all existing operations and to
   2571  // wait for all existing directory locks to be released as well.
   2572  //
   2573  // This might not be possible after adding mInitializingAllTemporaryOrigins
   2574  // to the checks below.
   2575 
   2576  const bool needsToWait = initiateShutdownWorkThreads() ||
   2577                           static_cast<bool>(gNormalOriginOps) ||
   2578                           mInitializingAllTemporaryOrigins;
   2579 
   2580  // If any clients cannot shutdown immediately, spin the event loop while we
   2581  // wait on all the threads to close.
   2582  if (needsToWait) {
   2583    startKillActorsTimer();
   2584 
   2585    MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
   2586        "QuotaManager::Shutdown"_ns, [this, isAllClientsShutdownComplete]() {
   2587          return !gNormalOriginOps && isAllClientsShutdownComplete() &&
   2588                 !mInitializingAllTemporaryOrigins;
   2589        }));
   2590 
   2591    stopKillActorsTimer();
   2592  }
   2593 
   2594  shutdownAndJoinWorkThreads();
   2595 
   2596  shutdownAndJoinIOThread();
   2597 
   2598  invalidatePendingDirectoryLocks();
   2599 
   2600  stopCrashBrowserTimer();
   2601 }
   2602 
   2603 void QuotaManager::InitQuotaForOrigin(
   2604    const FullOriginMetadata& aFullOriginMetadata, bool aDirectoryExists) {
   2605  AssertIsOnIOThread();
   2606  MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
   2607 
   2608  MutexAutoLock lock(mQuotaMutex);
   2609 
   2610  RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
   2611      aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix,
   2612      aFullOriginMetadata.mGroup);
   2613 
   2614  groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
   2615      groupInfo, aFullOriginMetadata.mOrigin,
   2616      aFullOriginMetadata.mStorageOrigin, aFullOriginMetadata.mIsPrivate,
   2617      aFullOriginMetadata.mClientUsages, aFullOriginMetadata.mOriginUsage,
   2618      aFullOriginMetadata.mLastAccessTime,
   2619      aFullOriginMetadata.mLastMaintenanceDate, aFullOriginMetadata.mPersisted,
   2620      aDirectoryExists));
   2621 }
   2622 
   2623 void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
   2624                                          int64_t aSize) {
   2625  MOZ_ASSERT(!NS_IsMainThread());
   2626  MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
   2627 
   2628  MutexAutoLock lock(mQuotaMutex);
   2629 
   2630  GroupInfoPair* pair;
   2631  if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
   2632    return;
   2633  }
   2634 
   2635  RefPtr<GroupInfo> groupInfo =
   2636      pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
   2637  if (!groupInfo) {
   2638    return;
   2639  }
   2640 
   2641  RefPtr<OriginInfo> originInfo =
   2642      groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
   2643  if (originInfo) {
   2644    originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize);
   2645  }
   2646 }
   2647 
   2648 void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) {
   2649  MOZ_ASSERT(!NS_IsMainThread());
   2650  MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
   2651 
   2652  MutexAutoLock lock(mQuotaMutex);
   2653 
   2654  GroupInfoPair* pair;
   2655  if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
   2656    return;
   2657  }
   2658 
   2659  RefPtr<GroupInfo> groupInfo =
   2660      pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
   2661  if (!groupInfo) {
   2662    return;
   2663  }
   2664 
   2665  RefPtr<OriginInfo> originInfo =
   2666      groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
   2667  if (originInfo) {
   2668    originInfo->LockedResetUsageForClient(aClientMetadata.mClientType);
   2669  }
   2670 }
   2671 
   2672 UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
   2673                                          const OriginMetadata& aOriginMetadata,
   2674                                          Client::Type aClientType) {
   2675  MOZ_ASSERT(!NS_IsMainThread());
   2676  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   2677 
   2678  MutexAutoLock lock(mQuotaMutex);
   2679 
   2680  GroupInfoPair* pair;
   2681  if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   2682    return UsageInfo{};
   2683  }
   2684 
   2685  RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
   2686  if (!groupInfo) {
   2687    return UsageInfo{};
   2688  }
   2689 
   2690  RefPtr<OriginInfo> originInfo =
   2691      groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
   2692  if (!originInfo) {
   2693    return UsageInfo{};
   2694  }
   2695 
   2696  return originInfo->LockedGetUsageForClient(aClientType);
   2697 }
   2698 
   2699 void QuotaManager::UpdateOriginAccessTime(const OriginMetadata& aOriginMetadata,
   2700                                          int64_t aTimestamp) {
   2701  AssertIsOnIOThread();
   2702  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   2703 
   2704  MutexAutoLock lock(mQuotaMutex);
   2705 
   2706  GroupInfoPair* pair;
   2707  if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   2708    return;
   2709  }
   2710 
   2711  RefPtr<GroupInfo> groupInfo =
   2712      pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType);
   2713  if (!groupInfo) {
   2714    return;
   2715  }
   2716 
   2717  RefPtr<OriginInfo> originInfo =
   2718      groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
   2719  if (!originInfo) {
   2720    return;
   2721  }
   2722 
   2723  originInfo->LockedUpdateAccessTime(aTimestamp);
   2724 }
   2725 
   2726 void QuotaManager::UpdateOriginMaintenanceDate(
   2727    const OriginMetadata& aOriginMetadata, int32_t aMaintenanceDate) {
   2728  AssertIsOnIOThread();
   2729  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   2730 
   2731  MutexAutoLock lock(mQuotaMutex);
   2732 
   2733  GroupInfoPair* pair;
   2734  if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   2735    return;
   2736  }
   2737 
   2738  RefPtr<GroupInfo> groupInfo =
   2739      pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType);
   2740  if (!groupInfo) {
   2741    return;
   2742  }
   2743 
   2744  RefPtr<OriginInfo> originInfo =
   2745      groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
   2746  if (!originInfo) {
   2747    return;
   2748  }
   2749 
   2750  originInfo->LockedUpdateMaintenanceDate(aMaintenanceDate);
   2751 }
   2752 
   2753 void QuotaManager::UpdateOriginAccessed(const OriginMetadata& aOriginMetadata) {
   2754  AssertIsOnIOThread();
   2755  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   2756 
   2757  MutexAutoLock lock(mQuotaMutex);
   2758 
   2759  GroupInfoPair* pair;
   2760  if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   2761    return;
   2762  }
   2763 
   2764  RefPtr<GroupInfo> groupInfo =
   2765      pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType);
   2766  if (!groupInfo) {
   2767    return;
   2768  }
   2769 
   2770  RefPtr<OriginInfo> originInfo =
   2771      groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
   2772  if (!originInfo) {
   2773    return;
   2774  }
   2775 
   2776  originInfo->LockedUpdateAccessed();
   2777 }
   2778 
   2779 void QuotaManager::RemoveQuota() {
   2780  AssertIsOnIOThread();
   2781 
   2782  MutexAutoLock lock(mQuotaMutex);
   2783 
   2784  for (const auto& entry : mGroupInfoPairs) {
   2785    const auto& pair = entry.GetData();
   2786 
   2787    MOZ_ASSERT(!entry.GetKey().IsEmpty());
   2788    MOZ_ASSERT(pair);
   2789 
   2790    RefPtr<GroupInfo> groupInfo =
   2791        pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
   2792    if (groupInfo) {
   2793      groupInfo->LockedRemoveOriginInfos();
   2794    }
   2795 
   2796    groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
   2797    if (groupInfo) {
   2798      groupInfo->LockedRemoveOriginInfos();
   2799    }
   2800 
   2801    groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
   2802    if (groupInfo) {
   2803      groupInfo->LockedRemoveOriginInfos();
   2804    }
   2805  }
   2806 
   2807  mGroupInfoPairs.Clear();
   2808 
   2809  MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!");
   2810 }
   2811 
   2812 // XXX Rename this method because the method doesn't load full quota
   2813 // information if origin initialization is done lazily.
   2814 nsresult QuotaManager::LoadQuota() {
   2815  GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::LoadQuota");
   2816 
   2817  AssertIsOnIOThread();
   2818  MOZ_ASSERT(mStorageConnection);
   2819  MOZ_ASSERT(!mTemporaryStorageInitializedInternal);
   2820 
   2821  // A list of all unaccessed default or temporary origins.
   2822  nsTArray<FullOriginMetadata> unaccessedOrigins;
   2823 
   2824  // XXX The list of all unaccessed default or temporary origins can be now
   2825  // generated from mAllTemporaryOrigins.
   2826  auto MaybeCollectUnaccessedOrigin =
   2827      [loadQuotaInfoStartTime = PR_Now(),
   2828       &unaccessedOrigins](const auto& fullOriginMetadata) {
   2829        if (IsOriginUnaccessed(fullOriginMetadata, loadQuotaInfoStartTime)) {
   2830          unaccessedOrigins.AppendElement(fullOriginMetadata);
   2831        }
   2832      };
   2833 
   2834  auto recordTimeDeltaHelper =
   2835      MakeRefPtr<RecordTimeDeltaHelper>(glean::dom_quota::info_load_time);
   2836 
   2837  const auto startTime = recordTimeDeltaHelper->Start();
   2838 
   2839  auto LoadQuotaFromCache = [&]() -> nsresult {
   2840    QM_TRY_INSPECT(
   2841        const auto& stmt,
   2842        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2843            nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
   2844            "SELECT repository_id, suffix, group_, "
   2845            "origin, client_usages, usage, "
   2846            "last_access_time, last_maintenance_date, accessed, persisted "
   2847            "FROM origin"_ns));
   2848 
   2849    auto autoRemoveQuota = MakeScopeExit([&] {
   2850      RemoveQuota();
   2851      RemoveTemporaryOrigins();
   2852      unaccessedOrigins.Clear();
   2853    });
   2854 
   2855    QM_TRY(quota::CollectWhileHasResult(
   2856        *stmt,
   2857        [this,
   2858         &MaybeCollectUnaccessedOrigin](auto& stmt) -> Result<Ok, nsresult> {
   2859          QM_TRY_INSPECT(const int32_t& repositoryId,
   2860                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
   2861 
   2862          const auto maybePersistenceType =
   2863              PersistenceTypeFromInt32(repositoryId, fallible);
   2864          QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
   2865 
   2866          FullOriginMetadata fullOriginMetadata;
   2867 
   2868          fullOriginMetadata.mPersistenceType = maybePersistenceType.value();
   2869 
   2870          QM_TRY_UNWRAP(fullOriginMetadata.mSuffix,
   2871                        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
   2872                                                          GetUTF8String, 1));
   2873 
   2874          QM_TRY_UNWRAP(fullOriginMetadata.mGroup,
   2875                        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
   2876                                                          GetUTF8String, 2));
   2877 
   2878          QM_TRY_UNWRAP(fullOriginMetadata.mOrigin,
   2879                        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
   2880                                                          GetUTF8String, 3));
   2881 
   2882          fullOriginMetadata.mStorageOrigin = fullOriginMetadata.mOrigin;
   2883 
   2884          const auto extraInfo =
   2885              ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
   2886                                 fullOriginMetadata.mStorageOrigin};
   2887 
   2888          fullOriginMetadata.mIsPrivate = false;
   2889 
   2890          QM_TRY_INSPECT(const auto& clientUsagesText,
   2891                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
   2892                                                           GetUTF8String, 4));
   2893 
   2894          ClientUsageArray clientUsages;
   2895          QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText)));
   2896 
   2897          fullOriginMetadata.mClientUsages = clientUsages;
   2898 
   2899          QM_TRY_INSPECT(fullOriginMetadata.mOriginUsage,
   2900                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 5));
   2901 
   2902          // fullOriginMetadata.mQuotaVersion is always set to kNoQuotaversion.
   2903          // It is not cached in the database, and caching it would not make
   2904          // sense because mQuotaVersion is currently unused by the L1 quota
   2905          // info cache. Validation for the L1 quota info cache is performed
   2906          // using the build ID instead.
   2907          fullOriginMetadata.mQuotaVersion = kNoQuotaVersion;
   2908 
   2909          QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
   2910                        MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 6));
   2911          QM_TRY_UNWRAP(fullOriginMetadata.mLastMaintenanceDate,
   2912                        MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 7));
   2913          QM_TRY_UNWRAP(fullOriginMetadata.mAccessed,
   2914                        MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 8));
   2915          QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
   2916                        MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 9));
   2917 
   2918          QM_TRY_INSPECT(const bool& groupUpdated,
   2919                         MaybeUpdateGroupForOrigin(fullOriginMetadata));
   2920 
   2921          (void)groupUpdated;
   2922 
   2923          QM_TRY_INSPECT(
   2924              const bool& lastAccessTimeUpdated,
   2925              MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
   2926 
   2927          (void)lastAccessTimeUpdated;
   2928 
   2929          // We don't need to update the .metadata-v2 file on disk here,
   2930          // EnsureTemporaryOriginIsInitializedInternal is responsible for
   2931          // doing that. We just need to use correct group and last access time
   2932          // before initializing quota for the given origin. (Note that calling
   2933          // LoadFullOriginMetadataWithRestore below might update the group in
   2934          // the metadata file, but only as a side-effect. The actual place we
   2935          // ensure consistency is in
   2936          // EnsureTemporaryOriginIsInitializedInternal.)
   2937 
   2938          if (fullOriginMetadata.mAccessed) {
   2939            QM_TRY_INSPECT(const auto& directory,
   2940                           GetOriginDirectory(fullOriginMetadata));
   2941 
   2942            QM_TRY_INSPECT(const bool& exists,
   2943                           MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
   2944 
   2945            QM_TRY(OkIf(exists), Err(NS_ERROR_FILE_NOT_FOUND));
   2946 
   2947            QM_TRY_INSPECT(const bool& isDirectory,
   2948                           MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory));
   2949 
   2950            QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FILE_DESTINATION_NOT_DIR));
   2951 
   2952            // Calling LoadFullOriginMetadataWithRestore might update the group
   2953            // in the metadata file, but only as a side-effect. The actual place
   2954            // we ensure consistency is in
   2955            // EnsureTemporaryOriginIsInitializedInternal.
   2956 
   2957            QM_TRY_INSPECT(const auto& metadata,
   2958                           LoadFullOriginMetadataWithRestore(directory));
   2959 
   2960            QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
   2961                                 metadata.mLastAccessTime));
   2962 
   2963            QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted),
   2964                   Err(NS_ERROR_FAILURE));
   2965 
   2966            // There was a previous regression where mLastAccessTime did not
   2967            // match. To avoid failing on similar non-critical mismatches, we
   2968            // wrap this check in a warn-only try macro for now.
   2969            QM_WARNONLY_TRY(
   2970                OkIf(fullOriginMetadata.mAccessed == metadata.mAccessed));
   2971 
   2972            // There was a previous regression where mLastAccessTime did not
   2973            // match. To avoid failing on similar non-critical mismatches, we
   2974            // wrap this check in a warn-only try macro for now.
   2975            QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastMaintenanceDate ==
   2976                                 metadata.mLastMaintenanceDate));
   2977 
   2978            QM_TRY(OkIf(fullOriginMetadata.mPersistenceType ==
   2979                        metadata.mPersistenceType),
   2980                   Err(NS_ERROR_FAILURE));
   2981 
   2982            QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix),
   2983                   Err(NS_ERROR_FAILURE));
   2984 
   2985            QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup),
   2986                   Err(NS_ERROR_FAILURE));
   2987 
   2988            QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin),
   2989                   Err(NS_ERROR_FAILURE));
   2990 
   2991            QM_TRY(OkIf(fullOriginMetadata.mStorageOrigin ==
   2992                        metadata.mStorageOrigin),
   2993                   Err(NS_ERROR_FAILURE));
   2994 
   2995            QM_TRY(OkIf(fullOriginMetadata.mIsPrivate == metadata.mIsPrivate),
   2996                   Err(NS_ERROR_FAILURE));
   2997 
   2998            // fullOriginMetadata.mQuotaVersion and metadata.mQuotaVersion do
   2999            // not need to match, since fullOriginMetadata.mQuotaVersion is
   3000            // always set to kNoQuotaVersion (see the comment above for more
   3001            // details about fullOriginMetadata.mQuotaVersion).
   3002 
   3003            // fullOriginMetadata.mOriginUsage and metadata.mOriginUsage do not
   3004            // need to match, since mClientUsages is currently not saved after
   3005            // last origin directory access.
   3006 
   3007            // fullOriginMetadata.mClientUsages and metadata.mClientUsages do
   3008            // not need to match, since mClientUsages is currently not saved
   3009            // after last origin directory access.
   3010 
   3011            MaybeCollectUnaccessedOrigin(metadata);
   3012 
   3013            AddTemporaryOrigin(metadata);
   3014 
   3015            QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, metadata)));
   3016          } else {
   3017            MaybeCollectUnaccessedOrigin(fullOriginMetadata);
   3018 
   3019            AddTemporaryOrigin(fullOriginMetadata);
   3020 
   3021            InitQuotaForOrigin(fullOriginMetadata);
   3022          }
   3023 
   3024          return Ok{};
   3025        }));
   3026 
   3027    autoRemoveQuota.release();
   3028 
   3029    return NS_OK;
   3030  };
   3031 
   3032  QM_TRY_INSPECT(
   3033      const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> {
   3034        if (mCacheUsable) {
   3035          QM_TRY_INSPECT(
   3036              const auto& stmt,
   3037              CreateAndExecuteSingleStepStatement<
   3038                  SingleStepResult::ReturnNullIfNoResult>(
   3039                  *mStorageConnection, "SELECT valid, build_id FROM cache"_ns));
   3040 
   3041          QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
   3042 
   3043          QM_TRY_INSPECT(const int32_t& valid,
   3044                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
   3045 
   3046          if (valid) {
   3047            if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) {
   3048              return true;
   3049            }
   3050 
   3051            QM_TRY_INSPECT(const auto& buildId,
   3052                           MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   3053                               nsAutoCString, stmt, GetUTF8String, 1));
   3054 
   3055            return buildId == *gBuildId;
   3056          }
   3057        }
   3058 
   3059        return false;
   3060      }()));
   3061 
   3062  auto autoRemoveQuota = MakeScopeExit([&] {
   3063    RemoveQuota();
   3064    RemoveTemporaryOrigins();
   3065  });
   3066 
   3067  if (!loadQuotaFromCache ||
   3068      !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
   3069      ![&LoadQuotaFromCache] {
   3070        QM_WARNONLY_TRY_UNWRAP(auto res, MOZ_TO_RESULT(LoadQuotaFromCache()));
   3071        return static_cast<bool>(res);
   3072      }()) {
   3073    // A keeper to defer the return only in Nightly, so that the telemetry data
   3074    // for whole profile can be collected.
   3075 #ifdef NIGHTLY_BUILD
   3076    nsresult statusKeeper = NS_OK;
   3077 #endif
   3078 
   3079    const auto statusKeeperFunc = [&](const nsresult rv) {
   3080      RECORD_IN_NIGHTLY(statusKeeper, rv);
   3081    };
   3082 
   3083    for (const PersistenceType type :
   3084         kInitializableBestEffortPersistenceTypes) {
   3085      if (NS_WARN_IF(IsShuttingDown())) {
   3086        RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
   3087      }
   3088 
   3089      QM_TRY(([&]() -> Result<Ok, nsresult> {
   3090        QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] {
   3091                 const auto innerFunc = [&](const auto&) -> nsresult {
   3092                   return InitializeRepository(type,
   3093                                               MaybeCollectUnaccessedOrigin);
   3094                 };
   3095 
   3096                 return ExecuteInitialization(
   3097                     type == PERSISTENCE_TYPE_DEFAULT
   3098                         ? Initialization::DefaultRepository
   3099                         : Initialization::TemporaryRepository,
   3100                     innerFunc);
   3101               }())),
   3102               OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
   3103 
   3104        return Ok{};
   3105      }()));
   3106    }
   3107 
   3108 #ifdef NIGHTLY_BUILD
   3109    if (NS_FAILED(statusKeeper)) {
   3110      return statusKeeper;
   3111    }
   3112 #endif
   3113  }
   3114 
   3115  autoRemoveQuota.release();
   3116 
   3117  const auto endTime = recordTimeDeltaHelper->End();
   3118 
   3119  if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() &&
   3120      static_cast<uint32_t>((endTime - startTime).ToMilliseconds()) >=
   3121          StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() &&
   3122      !unaccessedOrigins.IsEmpty()) {
   3123    QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins));
   3124  }
   3125 
   3126  return NS_OK;
   3127 }
   3128 
   3129 void QuotaManager::UnloadQuota() {
   3130  AssertIsOnIOThread();
   3131  MOZ_ASSERT(mStorageConnection);
   3132  MOZ_ASSERT(mTemporaryStorageInitializedInternal);
   3133  MOZ_ASSERT(mCacheUsable);
   3134 
   3135  auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
   3136 
   3137  mozStorageTransaction transaction(
   3138      mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
   3139 
   3140  QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID);
   3141 
   3142  QM_TRY(MOZ_TO_RESULT(
   3143             mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)),
   3144         QM_VOID);
   3145 
   3146  nsCOMPtr<mozIStorageStatement> insertStmt;
   3147 
   3148  {
   3149    MutexAutoLock lock(mQuotaMutex);
   3150 
   3151    for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
   3152      MOZ_ASSERT(!iter.Key().IsEmpty());
   3153 
   3154      GroupInfoPair* const pair = iter.UserData();
   3155      MOZ_ASSERT(pair);
   3156 
   3157      for (const PersistenceType type : kBestEffortPersistenceTypes) {
   3158        RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
   3159        if (!groupInfo) {
   3160          continue;
   3161        }
   3162 
   3163        for (const auto& originInfo : groupInfo->mOriginInfos) {
   3164          MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count());
   3165 
   3166          if (!originInfo->LockedDirectoryExists()) {
   3167            continue;
   3168          }
   3169 
   3170          if (originInfo->mIsPrivate) {
   3171            continue;
   3172          }
   3173 
   3174          if (insertStmt) {
   3175            MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
   3176          } else {
   3177            QM_TRY_UNWRAP(
   3178                insertStmt,
   3179                MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   3180                    nsCOMPtr<mozIStorageStatement>, mStorageConnection,
   3181                    CreateStatement,
   3182                    "INSERT INTO origin (repository_id, suffix, group_, "
   3183                    "origin, client_usages, usage, last_access_time, "
   3184                    "last_maintenance_date, accessed, persisted) "
   3185                    "VALUES (:repository_id, :suffix, :group_, :origin, "
   3186                    ":client_usages, :usage, :last_access_time, "
   3187                    ":last_maintenance_date, :accessed, :persisted)"_ns),
   3188                QM_VOID);
   3189          }
   3190 
   3191          QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)),
   3192                 QM_VOID);
   3193 
   3194          QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID);
   3195        }
   3196 
   3197        groupInfo->LockedRemoveOriginInfos();
   3198      }
   3199 
   3200      iter.Remove();
   3201    }
   3202  }
   3203 
   3204  QM_TRY_INSPECT(
   3205      const auto& stmt,
   3206      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   3207          nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
   3208          "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
   3209      QM_VOID);
   3210 
   3211  QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID);
   3212  QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)),
   3213         QM_VOID);
   3214  QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
   3215  QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
   3216 }
   3217 
   3218 void QuotaManager::RemoveOriginFromCache(
   3219    const OriginMetadata& aOriginMetadata) {
   3220  AssertIsOnIOThread();
   3221  MOZ_ASSERT(mStorageConnection);
   3222  MOZ_ASSERT(!mTemporaryStorageInitializedInternal);
   3223 
   3224  if (!mCacheUsable) {
   3225    return;
   3226  }
   3227 
   3228  mozStorageTransaction transaction(
   3229      mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
   3230 
   3231  QM_TRY_INSPECT(
   3232      const auto& stmt,
   3233      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   3234          nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
   3235          "DELETE FROM origin WHERE repository_id = :repository_id AND suffix = :suffix AND group_ = :group AND origin = :origin;"_ns),
   3236      QM_VOID);
   3237 
   3238  QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("repository_id"_ns,
   3239                                             aOriginMetadata.mPersistenceType)),
   3240         QM_VOID);
   3241  QM_TRY(MOZ_TO_RESULT(
   3242             stmt->BindUTF8StringByName("suffix"_ns, aOriginMetadata.mSuffix)),
   3243         QM_VOID);
   3244  QM_TRY(MOZ_TO_RESULT(
   3245             stmt->BindUTF8StringByName("group"_ns, aOriginMetadata.mGroup)),
   3246         QM_VOID);
   3247  QM_TRY(MOZ_TO_RESULT(
   3248             stmt->BindUTF8StringByName("origin"_ns, aOriginMetadata.mOrigin)),
   3249         QM_VOID);
   3250  QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
   3251 
   3252  QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
   3253 }
   3254 
   3255 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
   3256    PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
   3257    Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
   3258    int64_t* aFileSizeOut /* = nullptr */) {
   3259  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   3260  MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
   3261 
   3262  if (aFileSizeOut) {
   3263    *aFileSizeOut = 0;
   3264  }
   3265 
   3266  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
   3267    return nullptr;
   3268  }
   3269 
   3270  QM_TRY_INSPECT(const auto& path,
   3271                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aFile, GetPath),
   3272                 nullptr);
   3273 
   3274 #ifdef DEBUG
   3275  {
   3276    QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
   3277                   nullptr);
   3278 
   3279    nsAutoString clientType;
   3280    QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
   3281           nullptr);
   3282 
   3283    QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr);
   3284 
   3285    QM_TRY_INSPECT(
   3286        const auto& directoryPath,
   3287        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directory, GetPath),
   3288        nullptr);
   3289 
   3290    MOZ_ASSERT(StringBeginsWith(path, directoryPath));
   3291  }
   3292 #endif
   3293 
   3294  QM_TRY_INSPECT(
   3295      const int64_t fileSize,
   3296      ([&aFile, aFileSize]() -> Result<int64_t, nsresult> {
   3297        if (aFileSize == -1) {
   3298          QM_TRY_INSPECT(const bool& exists,
   3299                         MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
   3300 
   3301          if (exists) {
   3302            QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize));
   3303          }
   3304 
   3305          return 0;
   3306        }
   3307 
   3308        return aFileSize;
   3309      }()),
   3310      nullptr);
   3311 
   3312  RefPtr<QuotaObject> result;
   3313  {
   3314    MutexAutoLock lock(mQuotaMutex);
   3315 
   3316    GroupInfoPair* pair;
   3317    if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   3318      return nullptr;
   3319    }
   3320 
   3321    RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
   3322 
   3323    if (!groupInfo) {
   3324      return nullptr;
   3325    }
   3326 
   3327    RefPtr<OriginInfo> originInfo =
   3328        groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
   3329 
   3330    if (!originInfo) {
   3331      return nullptr;
   3332    }
   3333 
   3334    // We need this extra raw pointer because we can't assign to the smart
   3335    // pointer directly since QuotaObject::AddRef would try to acquire the same
   3336    // mutex.
   3337    const NotNull<CanonicalQuotaObject*> canonicalQuotaObject =
   3338        originInfo->mCanonicalQuotaObjects.LookupOrInsertWith(path, [&] {
   3339          // Create a new QuotaObject. The hashtable is not responsible to
   3340          // delete the QuotaObject.
   3341          return WrapNotNullUnchecked(new CanonicalQuotaObject(
   3342              originInfo, aClientType, path, fileSize));
   3343        });
   3344 
   3345    // Addref the QuotaObject and move the ownership to the result. This must
   3346    // happen before we unlock!
   3347    result = canonicalQuotaObject->LockedAddRef();
   3348  }
   3349 
   3350  if (aFileSizeOut) {
   3351    *aFileSizeOut = fileSize;
   3352  }
   3353 
   3354  // The caller becomes the owner of the QuotaObject, that is, the caller is
   3355  // is responsible to delete it when the last reference is removed.
   3356  return result.forget();
   3357 }
   3358 
   3359 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
   3360    PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
   3361    Client::Type aClientType, const nsAString& aPath, int64_t aFileSize,
   3362    int64_t* aFileSizeOut /* = nullptr */) {
   3363  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   3364 
   3365  if (aFileSizeOut) {
   3366    *aFileSizeOut = 0;
   3367  }
   3368 
   3369  QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr);
   3370 
   3371  return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file,
   3372                        aFileSize, aFileSizeOut);
   3373 }
   3374 
   3375 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
   3376    const int64_t aDirectoryLockId, const nsAString& aPath) {
   3377  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   3378 
   3379  Maybe<MutexAutoLock> lock;
   3380 
   3381  // See the comment for mDirectoryLockIdTable in QuotaManager.h
   3382  if (!IsOnBackgroundThread()) {
   3383    lock.emplace(mQuotaMutex);
   3384  }
   3385 
   3386  if (auto maybeDirectoryLock =
   3387          mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) {
   3388    const auto& directoryLock = *maybeDirectoryLock;
   3389    MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable());
   3390 
   3391    const PersistenceType persistenceType = directoryLock->GetPersistenceType();
   3392    const OriginMetadata& originMetadata = directoryLock->OriginMetadata();
   3393    const Client::Type clientType = directoryLock->ClientType();
   3394 
   3395    lock.reset();
   3396 
   3397    return GetQuotaObject(persistenceType, originMetadata, clientType, aPath);
   3398  }
   3399 
   3400  MOZ_ASSERT(aDirectoryLockId == -1);
   3401  return nullptr;
   3402 }
   3403 
   3404 Nullable<bool> QuotaManager::OriginPersisted(
   3405    const OriginMetadata& aOriginMetadata) {
   3406  AssertIsOnIOThread();
   3407 
   3408  MutexAutoLock lock(mQuotaMutex);
   3409 
   3410  RefPtr<OriginInfo> originInfo =
   3411      LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
   3412  if (originInfo) {
   3413    return Nullable<bool>(originInfo->LockedPersisted());
   3414  }
   3415 
   3416  return Nullable<bool>();
   3417 }
   3418 
   3419 void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) {
   3420  AssertIsOnIOThread();
   3421 
   3422  MutexAutoLock lock(mQuotaMutex);
   3423 
   3424  RefPtr<OriginInfo> originInfo =
   3425      LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
   3426  if (originInfo && !originInfo->LockedPersisted()) {
   3427    originInfo->LockedPersist();
   3428  }
   3429 }
   3430 
   3431 void QuotaManager::AbortOperationsForLocks(
   3432    const DirectoryLockIdTableArray& aLockIds) {
   3433  for (Client::Type type : AllClientTypes()) {
   3434    if (aLockIds[type].Filled()) {
   3435      (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]);
   3436    }
   3437  }
   3438 }
   3439 
   3440 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
   3441  AssertIsOnOwningThread();
   3442 
   3443  for (const RefPtr<Client>& client : *mClients) {
   3444    client->AbortOperationsForProcess(aContentParentId);
   3445  }
   3446 }
   3447 
   3448 Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetOriginDirectory(
   3449    const OriginMetadata& aOriginMetadata) const {
   3450  QM_TRY_UNWRAP(
   3451      auto directory,
   3452      QM_NewLocalFile(GetStoragePath(aOriginMetadata.mPersistenceType)));
   3453 
   3454  QM_TRY(MOZ_TO_RESULT(directory->Append(
   3455      MakeSanitizedOriginString(aOriginMetadata.mStorageOrigin))));
   3456 
   3457  return directory;
   3458 }
   3459 
   3460 Result<bool, nsresult> QuotaManager::DoesOriginDirectoryExist(
   3461    const OriginMetadata& aOriginMetadata) const {
   3462  AssertIsOnIOThread();
   3463 
   3464  QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata));
   3465 
   3466  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
   3467 }
   3468 
   3469 Result<nsCOMPtr<nsIFile>, nsresult>
   3470 QuotaManager::GetOrCreateTemporaryOriginDirectory(
   3471    const OriginMetadata& aOriginMetadata) {
   3472  AssertIsOnIOThread();
   3473  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   3474  MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
   3475  MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
   3476  MOZ_ASSERT(IsTemporaryGroupInitializedInternal(aOriginMetadata));
   3477  MOZ_ASSERT(IsTemporaryOriginInitializedInternal(aOriginMetadata));
   3478 
   3479  ScopedLogExtraInfo scope{
   3480      ScopedLogExtraInfo::kTagContextTainted,
   3481      "dom::quota::QuotaManager::GetOrCreateTemporaryOriginDirectory"_ns};
   3482 
   3483  // XXX Temporary band-aid fix until the root cause of uninitialized origins
   3484  // after obtaining a client directory lock via OpenClientDirectory is
   3485  // identified.
   3486  QM_TRY(
   3487      // Expression.
   3488      MOZ_TO_RESULT(IsTemporaryOriginInitializedInternal(aOriginMetadata))
   3489          .mapErr([](const nsresult rv) { return NS_ERROR_NOT_INITIALIZED; }),
   3490      // Custom return value.
   3491      QM_PROPAGATE,
   3492      // Cleanup function.
   3493      ([this, aOriginMetadata](const nsresult) {
   3494        MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(
   3495            NS_NewRunnableFunction(
   3496                "QuotaManager::GetOrCreateTemporaryOriginDirectory",
   3497                [aOriginMetadata]() {
   3498                  QuotaManager* quotaManager = QuotaManager::Get();
   3499                  MOZ_ASSERT(quotaManager);
   3500 
   3501                  OriginMetadataArray originMetadataArray;
   3502                  originMetadataArray.AppendElement(aOriginMetadata);
   3503 
   3504                  quotaManager->NoteUninitializedClients(originMetadataArray);
   3505                  quotaManager->NoteUninitializedOrigins(originMetadataArray);
   3506                }),
   3507            NS_DISPATCH_NORMAL));
   3508      }));
   3509 
   3510  QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
   3511 
   3512  QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
   3513 
   3514  if (created) {
   3515    // A new origin directory has been created.
   3516 
   3517    // We have a temporary origin which has been initialized without ensuring
   3518    // respective origin directory. So OriginInfo already exists and it needs
   3519    // to be updated because the origin directory has been just created.
   3520 
   3521    auto [timestamp, maintenanceDate, accessed, persisted] =
   3522        WithOriginInfo(aOriginMetadata, [](const auto& originInfo) {
   3523          const int64_t timestamp = originInfo->LockedAccessTime();
   3524          const int32_t maintenanceDate = originInfo->LockedMaintenanceDate();
   3525          const bool accessed = originInfo->LockedAccessed();
   3526          const bool persisted = originInfo->LockedPersisted();
   3527 
   3528          originInfo->LockedDirectoryCreated();
   3529 
   3530          return std::make_tuple(timestamp, maintenanceDate, accessed,
   3531                                 persisted);
   3532        });
   3533 
   3534    FullOriginMetadata fullOriginMetadata{
   3535        aOriginMetadata,
   3536        OriginStateMetadata{timestamp, maintenanceDate, accessed, persisted},
   3537        ClientUsageArray(), /* aUsage */ 0, kCurrentQuotaVersion};
   3538 
   3539    // Usually, infallible operations are placed after fallible ones. However,
   3540    // since we lack atomic support for creating the origin directory along
   3541    // with its metadata, we need to add the origin to cached origins right
   3542    // after directory creation.
   3543    AddTemporaryOrigin(fullOriginMetadata);
   3544 
   3545    QM_TRY(MOZ_TO_RESULT(
   3546        CreateDirectoryMetadata2(*directory, fullOriginMetadata)));
   3547  }
   3548 
   3549  return std::move(directory);
   3550 }
   3551 
   3552 Result<Ok, nsresult> QuotaManager::EnsureTemporaryOriginDirectoryCreated(
   3553    const OriginMetadata& aOriginMetadata) {
   3554  QM_TRY_RETURN(GetOrCreateTemporaryOriginDirectory(aOriginMetadata)
   3555                    .map([](const auto& res) { return Ok{}; }));
   3556 }
   3557 
   3558 // static
   3559 nsresult QuotaManager::CreateDirectoryMetadata2(
   3560    nsIFile& aDirectory, const FullOriginMetadata& aFullOriginMetadata) {
   3561  GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::CreateDirectoryMetadata2");
   3562 
   3563  AssertIsOnIOThread();
   3564 
   3565  QM_TRY(ArtificialFailure(
   3566      nsIQuotaArtificialFailure::CATEGORY_CREATE_DIRECTORY_METADATA2));
   3567 
   3568  QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   3569                                       nsCOMPtr<nsIFile>, aDirectory, Clone));
   3570 
   3571  QM_TRY(
   3572      MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME))));
   3573 
   3574  QM_TRY_INSPECT(const auto& stream,
   3575                 GetBinaryOutputStream(*file, FileFlag::Truncate));
   3576  MOZ_ASSERT(stream);
   3577 
   3578  QM_TRY(MOZ_TO_RESULT(
   3579      WriteDirectoryMetadataHeader(*stream, aFullOriginMetadata)));
   3580 
   3581  // Legacy field, previously used for suffix. The value is no longer used, but
   3582  // we continue writing the correct suffix value to preserve compatibility
   3583  // with older builds that may still expect it.
   3584  QM_TRY(
   3585      MOZ_TO_RESULT(stream->WriteStringZ(aFullOriginMetadata.mSuffix.get())));
   3586 
   3587  // Legacy field, previously used for group. The value is no longer used, but
   3588  // we continue writing the correct group value to preserve compatibility with
   3589  // older builds that may still expect it.
   3590  QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aFullOriginMetadata.mGroup.get())));
   3591 
   3592  QM_TRY(MOZ_TO_RESULT(
   3593      stream->WriteStringZ(aFullOriginMetadata.mStorageOrigin.get())));
   3594 
   3595  // Legacy field, previously used for isPrivate (and before that, for isApp).
   3596  // The value is no longer used, but we continue writing the correct isPrivate
   3597  // value (true or false) to preserve compatibility with older builds that may
   3598  // still expect it.
   3599  QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aFullOriginMetadata.mIsPrivate)));
   3600 
   3601  QM_TRY(MOZ_TO_RESULT(stream->Write32(kMetadataSentinel)));
   3602 
   3603  QM_TRY(MOZ_TO_RESULT(stream->Write32(aFullOriginMetadata.mQuotaVersion)));
   3604 
   3605  QM_TRY(MOZ_TO_RESULT(stream->Write64(aFullOriginMetadata.mOriginUsage)));
   3606 
   3607  nsCString clientUsagesText;
   3608  aFullOriginMetadata.mClientUsages.Serialize(clientUsagesText);
   3609 
   3610  QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(clientUsagesText.get())));
   3611 
   3612  QM_TRY(MOZ_TO_RESULT(stream->Flush()));
   3613 
   3614  QM_TRY(MOZ_TO_RESULT(stream->Close()));
   3615 
   3616  QM_TRY(MOZ_TO_RESULT(
   3617      file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME))));
   3618 
   3619  return NS_OK;
   3620 }
   3621 
   3622 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) {
   3623  AssertIsOnIOThread();
   3624  MOZ_ASSERT(aDirectory);
   3625  MOZ_ASSERT(mStorageConnection);
   3626 
   3627  glean::quotamanager::restore_origin_directory_metadata_counter.Add();
   3628 
   3629  RefPtr<RestoreDirectoryMetadata2Helper> helper =
   3630      new RestoreDirectoryMetadata2Helper(aDirectory);
   3631 
   3632  QM_TRY(MOZ_TO_RESULT(helper->Init()));
   3633 
   3634  QM_TRY(MOZ_TO_RESULT(helper->RestoreMetadata2File()));
   3635 
   3636  return NS_OK;
   3637 }
   3638 
   3639 Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata(
   3640    nsIFile* aDirectory, PersistenceType aPersistenceType) {
   3641  MOZ_ASSERT(!NS_IsMainThread());
   3642  MOZ_ASSERT(aDirectory);
   3643  MOZ_ASSERT(mStorageConnection);
   3644 
   3645  QM_TRY_INSPECT(const auto& binaryStream,
   3646                 GetBinaryInputStream(*aDirectory,
   3647                                      nsLiteralString(METADATA_V2_FILE_NAME)));
   3648 
   3649  FullOriginMetadata fullOriginMetadata;
   3650 
   3651  QM_TRY_INSPECT(const OriginStateMetadata& originStateMetadata,
   3652                 ReadDirectoryMetadataHeader(*binaryStream));
   3653 
   3654  static_cast<OriginStateMetadata&>(fullOriginMetadata) = originStateMetadata;
   3655 
   3656  fullOriginMetadata.mPersistenceType = aPersistenceType;
   3657 
   3658  // Legacy field, previously used for suffix. This value is no longer used,
   3659  // but still read and discarded to preserve compatibility with older builds
   3660  // that may still expect it.
   3661  QM_TRY_INSPECT(
   3662      const auto& unusedData1,
   3663      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
   3664  (void)unusedData1;
   3665 
   3666  // Legacy field, previously used for group. This value is no longer used, but
   3667  // still read and discarded to preserve compatibility with older builds that
   3668  // may still expect it.
   3669  QM_TRY_INSPECT(
   3670      const auto& unusedData2,
   3671      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
   3672  (void)unusedData2;
   3673 
   3674  QM_TRY_UNWRAP(
   3675      fullOriginMetadata.mStorageOrigin,
   3676      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
   3677 
   3678  const auto extraInfo =
   3679      ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
   3680                         fullOriginMetadata.mStorageOrigin};
   3681 
   3682  // Legacy field, previously used for isPrivate (and before that, for isApp).
   3683  // This value is no longer used, but still read and discarded to preserve
   3684  // compatibility with older builds that may still expect it.
   3685  QM_TRY_INSPECT(const bool& unusedData3,
   3686                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
   3687  (void)unusedData3;
   3688 
   3689  QM_VERBOSEONLY_TRY_UNWRAP(const auto sentinel,
   3690                            MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
   3691 
   3692  if (sentinel) {
   3693    if (sentinel.ref() != kMetadataSentinel) {
   3694      QM_TRY(MOZ_TO_RESULT(false));
   3695    }
   3696 
   3697    QM_TRY_UNWRAP(fullOriginMetadata.mQuotaVersion,
   3698                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
   3699 
   3700    QM_TRY_UNWRAP(fullOriginMetadata.mOriginUsage,
   3701                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
   3702 
   3703    QM_TRY_INSPECT(const auto& clientUsagesText,
   3704                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream,
   3705                                                     ReadCString));
   3706 
   3707    ClientUsageArray clientUsages;
   3708    QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText)));
   3709 
   3710    fullOriginMetadata.mClientUsages = clientUsages;
   3711  } else {
   3712    fullOriginMetadata.mQuotaVersion = kNoQuotaVersion;
   3713    fullOriginMetadata.mOriginUsage = 0;
   3714    fullOriginMetadata.mClientUsages = ClientUsageArray();
   3715  }
   3716 
   3717  QM_TRY(MOZ_TO_RESULT(binaryStream->Close()));
   3718 
   3719  auto principal =
   3720      [&storageOrigin =
   3721           fullOriginMetadata.mStorageOrigin]() -> nsCOMPtr<nsIPrincipal> {
   3722    if (storageOrigin.EqualsLiteral(kChromeOrigin)) {
   3723      return SystemPrincipal::Get();
   3724    }
   3725    return BasePrincipal::CreateContentPrincipal(storageOrigin);
   3726  }();
   3727  QM_TRY(MOZ_TO_RESULT(principal));
   3728 
   3729  PrincipalInfo principalInfo;
   3730  QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
   3731 
   3732  QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
   3733         Err(NS_ERROR_MALFORMED_URI));
   3734 
   3735  QM_TRY_UNWRAP(auto principalMetadata,
   3736                GetInfoFromValidatedPrincipalInfo(*this, principalInfo));
   3737 
   3738  fullOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix);
   3739  fullOriginMetadata.mGroup = std::move(principalMetadata.mGroup);
   3740  fullOriginMetadata.mOrigin = std::move(principalMetadata.mOrigin);
   3741  fullOriginMetadata.mIsPrivate = principalMetadata.mIsPrivate;
   3742 
   3743  QM_TRY_INSPECT(const bool& groupUpdated,
   3744                 MaybeUpdateGroupForOrigin(fullOriginMetadata));
   3745 
   3746  // A workaround for a bug in GetLastModifiedTime implementation which should
   3747  // have returned the current time instead of INT64_MIN when there were no
   3748  // suitable files for getting last modified time.
   3749  QM_TRY_INSPECT(const bool& lastAccessTimeUpdated,
   3750                 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
   3751 
   3752  if (groupUpdated || lastAccessTimeUpdated) {
   3753    // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
   3754    // I/O.
   3755    QM_TRY(MOZ_TO_RESULT(
   3756        CreateDirectoryMetadata2(*aDirectory, fullOriginMetadata)));
   3757  }
   3758 
   3759  return fullOriginMetadata;
   3760 }
   3761 
   3762 Result<FullOriginMetadata, nsresult>
   3763 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) {
   3764  QM_TRY_INSPECT(const auto& result,
   3765                 LoadFullOriginMetadataWithRestoreAndStatus(aDirectory));
   3766 
   3767  // The first element of the pair is the FullOriginMetadata, the second is the
   3768  // restore status.
   3769  return result.first;
   3770 }
   3771 
   3772 Result<std::pair<FullOriginMetadata, bool /* restore status */>, nsresult>
   3773 QuotaManager::LoadFullOriginMetadataWithRestoreAndStatus(nsIFile* aDirectory) {
   3774  // XXX Once the persistence type is stored in the metadata file, this block
   3775  // for getting the persistence type from the parent directory name can be
   3776  // removed.
   3777  nsCOMPtr<nsIFile> parentDir;
   3778  QM_TRY(MOZ_TO_RESULT(aDirectory->GetParent(getter_AddRefs(parentDir))));
   3779 
   3780  const auto maybePersistenceType =
   3781      PersistenceTypeFromFile(*parentDir, fallible);
   3782  QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
   3783 
   3784  const auto& persistenceType = maybePersistenceType.value();
   3785 
   3786  bool restored = false;
   3787 
   3788  QM_TRY_INSPECT(
   3789      const auto& fullOriginMetadata,
   3790      QM_OR_ELSE_WARN(
   3791          // Expression.
   3792          LoadFullOriginMetadata(aDirectory, persistenceType),
   3793          // Fallback.
   3794          ([&aDirectory, &persistenceType, &restored,
   3795            this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> {
   3796            restored = true;
   3797            QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory)));
   3798 
   3799            QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType));
   3800          })));
   3801 
   3802  return std::make_pair(fullOriginMetadata, restored);
   3803 }
   3804 
   3805 Result<OriginMetadata, nsresult> QuotaManager::GetOriginMetadata(
   3806    nsIFile* aDirectory) {
   3807  MOZ_ASSERT(aDirectory);
   3808  MOZ_ASSERT(mStorageConnection);
   3809 
   3810  QM_TRY_INSPECT(
   3811      const auto& leafName,
   3812      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aDirectory, GetLeafName));
   3813 
   3814  // XXX Consider using QuotaManager::ParseOrigin here.
   3815  nsCString spec;
   3816  OriginAttributes attrs;
   3817  nsCString originalSuffix;
   3818  OriginParser::ResultType result = OriginParser::ParseOrigin(
   3819      NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
   3820  QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
   3821 
   3822  QM_TRY_INSPECT(
   3823      const auto& principal,
   3824      ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
   3825        if (spec.EqualsLiteral(kChromeOrigin)) {
   3826          return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
   3827        }
   3828 
   3829        nsCOMPtr<nsIURI> uri;
   3830        QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
   3831 
   3832        return nsCOMPtr<nsIPrincipal>(
   3833            BasePrincipal::CreateContentPrincipal(uri, attrs));
   3834      }()));
   3835  QM_TRY(MOZ_TO_RESULT(principal));
   3836 
   3837  PrincipalInfo principalInfo;
   3838  QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
   3839 
   3840  QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
   3841         Err(NS_ERROR_MALFORMED_URI));
   3842 
   3843  QM_TRY_UNWRAP(auto principalMetadata,
   3844                GetInfoFromValidatedPrincipalInfo(*this, principalInfo));
   3845 
   3846  QM_TRY_INSPECT(const auto& parentDirectory,
   3847                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<nsIFile>,
   3848                                                   aDirectory, GetParent));
   3849 
   3850  const auto maybePersistenceType =
   3851      PersistenceTypeFromFile(*parentDirectory, fallible);
   3852  QM_TRY(MOZ_TO_RESULT(maybePersistenceType.isSome()));
   3853 
   3854  return OriginMetadata{std::move(principalMetadata),
   3855                        maybePersistenceType.value()};
   3856 }
   3857 
   3858 Result<Ok, nsresult> QuotaManager::RemoveOriginDirectory(nsIFile& aDirectory) {
   3859  AssertIsOnIOThread();
   3860 
   3861  if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) {
   3862    QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.Remove(true)));
   3863  }
   3864 
   3865  QM_TRY_INSPECT(const auto& toBeRemovedStorageDir,
   3866                 QM_NewLocalFile(*mToBeRemovedStoragePath));
   3867 
   3868  QM_TRY_INSPECT(const bool& created, EnsureDirectory(*toBeRemovedStorageDir));
   3869 
   3870  (void)created;
   3871 
   3872  QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.MoveTo(
   3873      toBeRemovedStorageDir, NSID_TrimBracketsUTF16(nsID::GenerateUUID()))));
   3874 }
   3875 
   3876 Result<bool, nsresult> QuotaManager::DoesClientDirectoryExist(
   3877    const ClientMetadata& aClientMetadata) const {
   3878  AssertIsOnIOThread();
   3879 
   3880  QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aClientMetadata));
   3881 
   3882  QM_TRY(MOZ_TO_RESULT(
   3883      directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
   3884 
   3885  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
   3886 }
   3887 
   3888 template <typename OriginFunc>
   3889 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType,
   3890                                            OriginFunc&& aOriginFunc) {
   3891  GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::InitializeRepository");
   3892 
   3893  AssertIsOnIOThread();
   3894  MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_PERSISTENT ||
   3895             aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
   3896             aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
   3897 
   3898  QM_TRY_INSPECT(const auto& directory,
   3899                 QM_NewLocalFile(GetStoragePath(aPersistenceType)));
   3900 
   3901  QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory));
   3902 
   3903  (void)created;
   3904 
   3905  uint64_t iterations = 0;
   3906 
   3907  // A keeper to defer the return only in Nightly, so that the telemetry data
   3908  // for whole profile can be collected
   3909 #ifdef NIGHTLY_BUILD
   3910  nsresult statusKeeper = NS_OK;
   3911 #endif
   3912 
   3913  const auto statusKeeperFunc = [&](const nsresult rv) {
   3914    RECORD_IN_NIGHTLY(statusKeeper, rv);
   3915  };
   3916 
   3917  struct RenameAndInitInfo {
   3918    nsCOMPtr<nsIFile> mOriginDirectory;
   3919    FullOriginMetadata mFullOriginMetadata;
   3920  };
   3921  nsTArray<RenameAndInitInfo> renameAndInitInfos;
   3922 
   3923  QM_TRY(([&]() -> Result<Ok, nsresult> {
   3924    QM_TRY(
   3925        CollectEachFile(
   3926            *directory,
   3927            [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> {
   3928              if (NS_WARN_IF(IsShuttingDown())) {
   3929                RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
   3930              }
   3931 
   3932              QM_TRY(
   3933                  ([this, &iterations, &childDirectory, &renameAndInitInfos,
   3934                    aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> {
   3935                    QM_TRY_INSPECT(
   3936                        const auto& leafName,
   3937                        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   3938                            nsAutoString, childDirectory, GetLeafName));
   3939 
   3940                    QM_TRY_INSPECT(const auto& dirEntryKind,
   3941                                   GetDirEntryKind(*childDirectory));
   3942 
   3943                    switch (dirEntryKind) {
   3944                      case nsIFileKind::ExistsAsDirectory: {
   3945                        QM_TRY_UNWRAP(
   3946                            auto maybeMetadata,
   3947                            QM_OR_ELSE_WARN_IF(
   3948                                // Expression
   3949                                LoadFullOriginMetadataWithRestore(
   3950                                    childDirectory)
   3951                                    .map([](auto metadata)
   3952                                             -> Maybe<FullOriginMetadata> {
   3953                                      return Some(std::move(metadata));
   3954                                    }),
   3955                                // Predicate.
   3956                                IsSpecificError<NS_ERROR_MALFORMED_URI>,
   3957                                // Fallback.
   3958                                ErrToDefaultOk<Maybe<FullOriginMetadata>>));
   3959 
   3960                        if (!maybeMetadata) {
   3961                          // Unknown directories during initialization are
   3962                          // allowed. Just warn if we find them.
   3963                          UNKNOWN_FILE_WARNING(leafName);
   3964                          break;
   3965                        }
   3966 
   3967                        auto metadata = maybeMetadata.extract();
   3968 
   3969                        MOZ_ASSERT(metadata.mPersistenceType ==
   3970                                   aPersistenceType);
   3971 
   3972                        const auto extraInfo = ScopedLogExtraInfo{
   3973                            ScopedLogExtraInfo::kTagStorageOriginTainted,
   3974                            metadata.mStorageOrigin};
   3975 
   3976                        // FIXME(tt): The check for origin name consistency can
   3977                        // be removed once we have an upgrade to traverse origin
   3978                        // directories and check through the directory metadata
   3979                        // files.
   3980                        const auto originSanitized =
   3981                            MakeSanitizedOriginCString(metadata.mOrigin);
   3982 
   3983                        NS_ConvertUTF16toUTF8 utf8LeafName(leafName);
   3984                        if (!originSanitized.Equals(utf8LeafName)) {
   3985                          QM_WARNING(
   3986                              "The name of the origin directory (%s) doesn't "
   3987                              "match the sanitized origin string (%s) in the "
   3988                              "metadata file!",
   3989                              utf8LeafName.get(), originSanitized.get());
   3990 
   3991                          // If it's the known case, we try to restore the
   3992                          // origin directory name if it's possible.
   3993                          if (originSanitized.Equals(utf8LeafName + "."_ns)) {
   3994                            renameAndInitInfos.AppendElement(
   3995                                RenameAndInitInfo{std::move(childDirectory),
   3996                                                  std::move(metadata)});
   3997                            break;
   3998                          }
   3999 
   4000                          // XXXtt: Try to restore the unknown cases base on the
   4001                          // content for their metadata files. Note that if the
   4002                          // restore fails, QM should maintain a list and ensure
   4003                          // they won't be accessed after initialization.
   4004                        }
   4005 
   4006                        if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
   4007                          std::forward<OriginFunc>(aOriginFunc)(metadata);
   4008 
   4009                          AddTemporaryOrigin(metadata);
   4010 
   4011                          if (StaticPrefs::
   4012                                  dom_quotaManager_loadQuotaFromSecondaryCache() &&
   4013                              metadata.mQuotaVersion == kCurrentQuotaVersion &&
   4014                              !metadata.mAccessed) {
   4015                            InitQuotaForOrigin(metadata);
   4016 
   4017                            break;
   4018                          }
   4019                        }
   4020 
   4021                        QM_TRY(QM_OR_ELSE_WARN_IF(
   4022                            // Expression.
   4023                            MOZ_TO_RESULT(
   4024                                InitializeOrigin(childDirectory, metadata)),
   4025                            // Predicate.
   4026                            IsDatabaseCorruptionError,
   4027                            // Fallback.
   4028                            ([&childDirectory, &metadata,
   4029                              this](const nsresult rv) -> Result<Ok, nsresult> {
   4030                              // If the origin can't be initialized due to
   4031                              // corruption, this is a permanent
   4032                              // condition, and we need to remove all data
   4033                              // for the origin on disk.
   4034 
   4035                              QM_TRY(
   4036                                  MOZ_TO_RESULT(childDirectory->Remove(true)));
   4037 
   4038                              RemoveTemporaryOrigin(metadata);
   4039 
   4040                              return Ok{};
   4041                            })));
   4042 
   4043                        break;
   4044                      }
   4045 
   4046                      case nsIFileKind::ExistsAsFile:
   4047                        if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
   4048                          break;
   4049                        }
   4050 
   4051                        // Unknown files during initialization are now allowed.
   4052                        // Just warn if we find them.
   4053                        UNKNOWN_FILE_WARNING(leafName);
   4054                        break;
   4055 
   4056                      case nsIFileKind::DoesNotExist:
   4057                        // Ignore files that got removed externally while
   4058                        // iterating.
   4059                        break;
   4060                    }
   4061 
   4062                    iterations++;
   4063 
   4064                    return Ok{};
   4065                  }()),
   4066                  OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
   4067 
   4068              return Ok{};
   4069            }),
   4070        OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
   4071 
   4072    return Ok{};
   4073  }()));
   4074 
   4075  for (auto& info : renameAndInitInfos) {
   4076    QM_TRY(([&]() -> Result<Ok, nsresult> {
   4077      QM_TRY(
   4078          ([&directory, &info, this, aPersistenceType,
   4079            &aOriginFunc]() -> Result<Ok, nsresult> {
   4080            const auto& metadata = info.mFullOriginMetadata;
   4081 
   4082            const auto extraInfo =
   4083                ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
   4084                                   metadata.mStorageOrigin};
   4085 
   4086            const auto originDirName =
   4087                MakeSanitizedOriginString(metadata.mOrigin);
   4088 
   4089            // Check if targetDirectory exist.
   4090            QM_TRY_INSPECT(const auto& targetDirectory,
   4091                           CloneFileAndAppend(*directory, originDirName));
   4092 
   4093            QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(
   4094                                                   targetDirectory, Exists));
   4095 
   4096            if (exists) {
   4097              QM_TRY(MOZ_TO_RESULT(info.mOriginDirectory->Remove(true)));
   4098 
   4099              return Ok{};
   4100            }
   4101 
   4102            QM_TRY(MOZ_TO_RESULT(
   4103                info.mOriginDirectory->RenameTo(nullptr, originDirName)));
   4104 
   4105            if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
   4106              std::forward<OriginFunc>(aOriginFunc)(metadata);
   4107 
   4108              AddTemporaryOrigin(metadata);
   4109 
   4110              if (StaticPrefs::dom_quotaManager_loadQuotaFromSecondaryCache() &&
   4111                  metadata.mQuotaVersion == kCurrentQuotaVersion &&
   4112                  !metadata.mAccessed) {
   4113                InitQuotaForOrigin(metadata);
   4114 
   4115                return Ok{};
   4116              }
   4117            }
   4118 
   4119            // XXX We don't check corruption here ?
   4120            QM_TRY(MOZ_TO_RESULT(InitializeOrigin(targetDirectory, metadata)));
   4121 
   4122            return Ok{};
   4123          }()),
   4124          OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
   4125 
   4126      return Ok{};
   4127    }()));
   4128  }
   4129 
   4130 #ifdef NIGHTLY_BUILD
   4131  if (NS_FAILED(statusKeeper)) {
   4132    return statusKeeper;
   4133  }
   4134 #endif
   4135 
   4136  glean::quotamanager_initialize_repository::number_of_iterations
   4137      .Get(PersistenceTypeToString(aPersistenceType))
   4138      .AccumulateSingleSample(iterations);
   4139 
   4140  return NS_OK;
   4141 }
   4142 
   4143 nsresult QuotaManager::InitializeOrigin(
   4144    nsIFile* aDirectory, const FullOriginMetadata& aFullOriginMetadata,
   4145    bool aForGroup) {
   4146  GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::InitializeOrigin");
   4147 
   4148  QM_LOG(("Starting origin initialization for: %s",
   4149          aFullOriginMetadata.mOrigin.get()));
   4150 
   4151  AssertIsOnIOThread();
   4152 
   4153  QM_TRY(
   4154      ArtificialFailure(nsIQuotaArtificialFailure::CATEGORY_INITIALIZE_ORIGIN));
   4155 
   4156  // The ScopedLogExtraInfo is not set here on purpose, so the callers can
   4157  // decide if they want to set it. The extra info can be set sooner this way
   4158  // as well.
   4159 
   4160  const bool trackQuota =
   4161      aFullOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
   4162 
   4163  if (trackQuota && !aForGroup &&
   4164      QuotaPrefs::LazyOriginInitializationEnabled()) {
   4165    QM_LOG(("Skipping origin initialization for: %s (it will be done lazily)",
   4166            aFullOriginMetadata.mOrigin.get()));
   4167 
   4168    return NS_OK;
   4169  }
   4170 
   4171  NotifyOriginInitializationStarted(*this);
   4172 
   4173  // We need to initialize directories of all clients if they exists and also
   4174  // get the total usage to initialize the quota.
   4175 
   4176  ClientUsageArray clientUsages;
   4177 
   4178  // A keeper to defer the return only in Nightly, so that the telemetry data
   4179  // for whole profile can be collected
   4180 #ifdef NIGHTLY_BUILD
   4181  nsresult statusKeeper = NS_OK;
   4182 #endif
   4183 
   4184  QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) {
   4185            RECORD_IN_NIGHTLY(statusKeeper, rv);
   4186          }]() -> Result<Ok, nsresult> {
   4187    QM_TRY(
   4188        CollectEachFile(
   4189            *aDirectory,
   4190            [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
   4191              if (NS_WARN_IF(IsShuttingDown())) {
   4192                RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
   4193              }
   4194 
   4195              QM_TRY(
   4196                  ([this, &file, trackQuota, &aFullOriginMetadata,
   4197                    &clientUsages]() -> Result<Ok, nsresult> {
   4198                    QM_TRY_INSPECT(const auto& leafName,
   4199                                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   4200                                       nsAutoString, file, GetLeafName));
   4201 
   4202                    QM_TRY_INSPECT(const auto& dirEntryKind,
   4203                                   GetDirEntryKind(*file));
   4204 
   4205                    switch (dirEntryKind) {
   4206                      case nsIFileKind::ExistsAsDirectory: {
   4207                        Client::Type clientType;
   4208                        const bool ok = Client::TypeFromText(
   4209                            leafName, clientType, fallible);
   4210                        if (!ok) {
   4211                          // Unknown directories during initialization are now
   4212                          // allowed. Just warn if we find them.
   4213                          UNKNOWN_FILE_WARNING(leafName);
   4214                          break;
   4215                        }
   4216 
   4217                        if (trackQuota) {
   4218                          QM_TRY_INSPECT(
   4219                              const auto& usageInfo,
   4220                              (*mClients)[clientType]->InitOrigin(
   4221                                  aFullOriginMetadata.mPersistenceType,
   4222                                  aFullOriginMetadata,
   4223                                  /* aCanceled */ Atomic<bool>(false)));
   4224 
   4225                          MOZ_ASSERT(!clientUsages[clientType]);
   4226 
   4227                          if (usageInfo.TotalUsage()) {
   4228                            // XXX(Bug 1683863) Until we identify the root cause
   4229                            // of seemingly converted-from-negative usage
   4230                            // values, we will just treat them as unset here,
   4231                            // but log a warning to the browser console.
   4232                            if (static_cast<int64_t>(*usageInfo.TotalUsage()) >=
   4233                                0) {
   4234                              clientUsages[clientType] = usageInfo.TotalUsage();
   4235                            } else {
   4236 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
   4237                              const nsCOMPtr<nsIConsoleService> console =
   4238                                  do_GetService(NS_CONSOLESERVICE_CONTRACTID);
   4239                              if (console) {
   4240                                console->LogStringMessage(
   4241                                    nsString(
   4242                                        u"QuotaManager warning: client "_ns +
   4243                                        leafName +
   4244                                        u" reported negative usage for group "_ns +
   4245                                        NS_ConvertUTF8toUTF16(
   4246                                            aFullOriginMetadata.mGroup) +
   4247                                        u", origin "_ns +
   4248                                        NS_ConvertUTF8toUTF16(
   4249                                            aFullOriginMetadata.mOrigin))
   4250                                        .get());
   4251                              }
   4252 #endif
   4253                            }
   4254                          }
   4255                        } else {
   4256                          QM_TRY(MOZ_TO_RESULT(
   4257                              (*mClients)[clientType]
   4258                                  ->InitOriginWithoutTracking(
   4259                                      aFullOriginMetadata.mPersistenceType,
   4260                                      aFullOriginMetadata,
   4261                                      /* aCanceled */ Atomic<bool>(false))));
   4262                        }
   4263 
   4264                        break;
   4265                      }
   4266 
   4267                      case nsIFileKind::ExistsAsFile:
   4268                        if (IsOriginMetadata(leafName)) {
   4269                          break;
   4270                        }
   4271 
   4272                        if (IsTempMetadata(leafName)) {
   4273                          QM_TRY(MOZ_TO_RESULT(
   4274                              file->Remove(/* recursive */ false)));
   4275 
   4276                          break;
   4277                        }
   4278 
   4279                        if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
   4280                          break;
   4281                        }
   4282 
   4283                        // Unknown files during initialization are now allowed.
   4284                        // Just warn if we find them.
   4285                        UNKNOWN_FILE_WARNING(leafName);
   4286                        // Bug 1595448 will handle the case for unknown files
   4287                        // like idb, cache, or ls.
   4288                        break;
   4289 
   4290                      case nsIFileKind::DoesNotExist:
   4291                        // Ignore files that got removed externally while
   4292                        // iterating.
   4293                        break;
   4294                    }
   4295 
   4296                    return Ok{};
   4297                  }()),
   4298                  OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
   4299 
   4300              return Ok{};
   4301            }),
   4302        OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
   4303 
   4304    return Ok{};
   4305  }()));
   4306 
   4307 #ifdef NIGHTLY_BUILD
   4308  if (NS_FAILED(statusKeeper)) {
   4309    return statusKeeper;
   4310  }
   4311 #endif
   4312 
   4313  if (trackQuota) {
   4314    const auto usage = std::accumulate(
   4315        clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0),
   4316        [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) {
   4317          return value + clientUsage.valueOr(0);
   4318        });
   4319 
   4320    // XXX Should we log more information, i.e. the whole clientUsages array, in
   4321    // case usage is not valid?
   4322 
   4323    QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE);
   4324 
   4325    FullOriginMetadata fullOriginMetadata = aFullOriginMetadata.Clone();
   4326 
   4327    fullOriginMetadata.mAccessed = false;
   4328 
   4329    fullOriginMetadata.mQuotaVersion = kCurrentQuotaVersion;
   4330    fullOriginMetadata.mOriginUsage = usage.value();
   4331    fullOriginMetadata.mClientUsages = clientUsages;
   4332 
   4333    if (StaticPrefs::
   4334            dom_quotaManager_originInitialization_updateOriginMetadata() &&
   4335        !fullOriginMetadata.Equals(aFullOriginMetadata)) {
   4336      // If the pref is enabled and the current metadata differs from what's
   4337      // stored, update the metadata file to reflect the most recent state.
   4338      // This is essential for ensuring correctness of the L2 quota info cache.
   4339 
   4340      if (fullOriginMetadata.EqualsIgnoringOriginState(aFullOriginMetadata)) {
   4341        // If only the OriginStateMetadata (header) differs, we can perform a
   4342        // fast, crash-safe in-place update of just the header.
   4343 
   4344        QM_TRY(MOZ_TO_RESULT(
   4345            SaveDirectoryMetadataHeader(*aDirectory, fullOriginMetadata)));
   4346 
   4347      } else {
   4348        // Otherwise, we fall back to recreating the full metadata file using a
   4349        // temporary file and atomic rename, which is slower but safe for
   4350        // structural changes.
   4351 
   4352        QM_TRY(MOZ_TO_RESULT(
   4353            CreateDirectoryMetadata2(*aDirectory, fullOriginMetadata)));
   4354      }
   4355    }
   4356 
   4357    InitQuotaForOrigin(fullOriginMetadata);
   4358  }
   4359 
   4360  SleepIfEnabled(
   4361      StaticPrefs::dom_quotaManager_originInitialization_pauseOnIOThreadMs());
   4362 
   4363  QM_LOG(("Ending origin initialization for: %s",
   4364          aFullOriginMetadata.mOrigin.get()));
   4365 
   4366  return NS_OK;
   4367 }
   4368 
   4369 nsresult
   4370 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
   4371    nsIFile* aIndexedDBDir) {
   4372  AssertIsOnIOThread();
   4373  MOZ_ASSERT(aIndexedDBDir);
   4374 
   4375  const auto innerFunc = [this, &aIndexedDBDir](const auto&) -> nsresult {
   4376    bool isDirectory;
   4377    QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->IsDirectory(&isDirectory)));
   4378 
   4379    if (!isDirectory) {
   4380      NS_WARNING("indexedDB entry is not a directory!");
   4381      return NS_OK;
   4382    }
   4383 
   4384    auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath);
   4385    if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) {
   4386      return persistentStorageDirOrErr.unwrapErr();
   4387    }
   4388 
   4389    nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap();
   4390 
   4391    QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
   4392        nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
   4393 
   4394    bool exists;
   4395    QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Exists(&exists)));
   4396 
   4397    if (exists) {
   4398      QM_WARNING("Deleting old <profile>/indexedDB directory!");
   4399 
   4400      QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->Remove(/* aRecursive */ true)));
   4401 
   4402      return NS_OK;
   4403    }
   4404 
   4405    nsCOMPtr<nsIFile> storageDir;
   4406    QM_TRY(MOZ_TO_RESULT(
   4407        persistentStorageDir->GetParent(getter_AddRefs(storageDir))));
   4408 
   4409    // MoveTo() is atomic if the move happens on the same volume which should
   4410    // be our case, so even if we crash in the middle of the operation nothing
   4411    // breaks next time we try to initialize.
   4412    // However there's a theoretical possibility that the indexedDB directory
   4413    // is on different volume, but it should be rare enough that we don't have
   4414    // to worry about it.
   4415    QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->MoveTo(
   4416        storageDir, nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
   4417 
   4418    return NS_OK;
   4419  };
   4420 
   4421  return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory,
   4422                               innerFunc);
   4423 }
   4424 
   4425 nsresult
   4426 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
   4427    nsIFile* aPersistentStorageDir) {
   4428  AssertIsOnIOThread();
   4429  MOZ_ASSERT(aPersistentStorageDir);
   4430 
   4431  const auto innerFunc = [this,
   4432                          &aPersistentStorageDir](const auto&) -> nsresult {
   4433    QM_TRY_INSPECT(
   4434        const bool& isDirectory,
   4435        MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir, IsDirectory));
   4436 
   4437    if (!isDirectory) {
   4438      NS_WARNING("persistent entry is not a directory!");
   4439      return NS_OK;
   4440    }
   4441 
   4442    {
   4443      QM_TRY_INSPECT(const auto& defaultStorageDir,
   4444                     QM_NewLocalFile(*mDefaultStoragePath));
   4445 
   4446      QM_TRY_INSPECT(const bool& exists,
   4447                     MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
   4448 
   4449      if (exists) {
   4450        QM_WARNING("Deleting old <profile>/storage/persistent directory!");
   4451 
   4452        QM_TRY(MOZ_TO_RESULT(
   4453            aPersistentStorageDir->Remove(/* aRecursive */ true)));
   4454 
   4455        return NS_OK;
   4456      }
   4457    }
   4458 
   4459    {
   4460      // Create real metadata files for origin directories in persistent
   4461      // storage.
   4462      auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
   4463          aPersistentStorageDir);
   4464 
   4465      QM_TRY(MOZ_TO_RESULT(helper->Init()));
   4466 
   4467      QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
   4468 
   4469      // Upgrade metadata files for origin directories in temporary storage.
   4470      QM_TRY_INSPECT(const auto& temporaryStorageDir,
   4471                     QM_NewLocalFile(*mTemporaryStoragePath));
   4472 
   4473      QM_TRY_INSPECT(const bool& exists,
   4474                     MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, Exists));
   4475 
   4476      if (exists) {
   4477        QM_TRY_INSPECT(
   4478            const bool& isDirectory,
   4479            MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, IsDirectory));
   4480 
   4481        if (!isDirectory) {
   4482          NS_WARNING("temporary entry is not a directory!");
   4483          return NS_OK;
   4484        }
   4485 
   4486        helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
   4487            temporaryStorageDir);
   4488 
   4489        QM_TRY(MOZ_TO_RESULT(helper->Init()));
   4490 
   4491        QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
   4492      }
   4493    }
   4494 
   4495    // And finally rename persistent to default.
   4496    QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir->RenameTo(
   4497        nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME))));
   4498 
   4499    return NS_OK;
   4500  };
   4501 
   4502  return ExecuteInitialization(
   4503      Initialization::UpgradeFromPersistentStorageDirectory, innerFunc);
   4504 }
   4505 
   4506 template <typename Helper>
   4507 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
   4508                                      const int32_t aNewVersion,
   4509                                      mozIStorageConnection* aConnection) {
   4510  AssertIsOnIOThread();
   4511  MOZ_ASSERT(aNewVersion > aOldVersion);
   4512  MOZ_ASSERT(aNewVersion <= kStorageVersion);
   4513  MOZ_ASSERT(aConnection);
   4514 
   4515  for (const PersistenceType persistenceType : kAllPersistenceTypesButPrivate) {
   4516    QM_TRY_UNWRAP(auto directory,
   4517                  QM_NewLocalFile(GetStoragePath(persistenceType)));
   4518 
   4519    QM_TRY_INSPECT(const bool& exists,
   4520                   MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
   4521 
   4522    if (!exists) {
   4523      continue;
   4524    }
   4525 
   4526    RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory);
   4527 
   4528    QM_TRY(MOZ_TO_RESULT(helper->Init()));
   4529 
   4530    QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
   4531  }
   4532 
   4533 #ifdef DEBUG
   4534  {
   4535    QM_TRY_INSPECT(const int32_t& storageVersion,
   4536                   MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
   4537 
   4538    MOZ_ASSERT(storageVersion == aOldVersion);
   4539  }
   4540 #endif
   4541 
   4542  QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(aNewVersion)));
   4543 
   4544  return NS_OK;
   4545 }
   4546 
   4547 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
   4548    mozIStorageConnection* aConnection) {
   4549  AssertIsOnIOThread();
   4550  MOZ_ASSERT(aConnection);
   4551 
   4552  const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
   4553    QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
   4554        0, MakeStorageVersion(1, 0), aConnection)));
   4555 
   4556    return NS_OK;
   4557  };
   4558 
   4559  return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0,
   4560                               innerFunc);
   4561 }
   4562 
   4563 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
   4564    mozIStorageConnection* aConnection) {
   4565  AssertIsOnIOThread();
   4566  MOZ_ASSERT(aConnection);
   4567 
   4568  // The upgrade consists of a number of logically distinct bugs that
   4569  // intentionally got fixed at the same time to trigger just one major
   4570  // version bump.
   4571  //
   4572  //
   4573  // Morgue directory cleanup
   4574  // [Feature/Bug]:
   4575  // The original bug that added "on demand" morgue cleanup is 1165119.
   4576  //
   4577  // [Mutations]:
   4578  // Morgue directories are removed from all origin directories during the
   4579  // upgrade process. Origin initialization and usage calculation doesn't try
   4580  // to remove morgue directories anymore.
   4581  //
   4582  // [Downgrade-incompatible changes]:
   4583  // Morgue directories can reappear if user runs an already upgraded profile
   4584  // in an older version of Firefox. Morgue directories then prevent current
   4585  // Firefox from initializing and using the storage.
   4586  //
   4587  //
   4588  // App data removal
   4589  // [Feature/Bug]:
   4590  // The bug that removes isApp flags is 1311057.
   4591  //
   4592  // [Mutations]:
   4593  // Origin directories with appIds are removed during the upgrade process.
   4594  //
   4595  // [Downgrade-incompatible changes]:
   4596  // Origin directories with appIds can reappear if user runs an already
   4597  // upgraded profile in an older version of Firefox. Origin directories with
   4598  // appIds don't prevent current Firefox from initializing and using the
   4599  // storage, but they wouldn't ever be removed again, potentially causing
   4600  // problems once appId is removed from origin attributes.
   4601  //
   4602  //
   4603  // Strip obsolete origin attributes
   4604  // [Feature/Bug]:
   4605  // The bug that strips obsolete origin attributes is 1314361.
   4606  //
   4607  // [Mutations]:
   4608  // Origin directories with obsolete origin attributes are renamed and their
   4609  // metadata files are updated during the upgrade process.
   4610  //
   4611  // [Downgrade-incompatible changes]:
   4612  // Origin directories with obsolete origin attributes can reappear if user
   4613  // runs an already upgraded profile in an older version of Firefox. Origin
   4614  // directories with obsolete origin attributes don't prevent current Firefox
   4615  // from initializing and using the storage, but they wouldn't ever be upgraded
   4616  // again, potentially causing problems in future.
   4617  //
   4618  //
   4619  // File manager directory renaming (client specific)
   4620  // [Feature/Bug]:
   4621  // The original bug that added "on demand" file manager directory renaming is
   4622  // 1056939.
   4623  //
   4624  // [Mutations]:
   4625  // All file manager directories are renamed to contain the ".files" suffix.
   4626  //
   4627  // [Downgrade-incompatible changes]:
   4628  // File manager directories with the ".files" suffix prevent older versions of
   4629  // Firefox from initializing and using the storage.
   4630  // File manager directories without the ".files" suffix can appear if user
   4631  // runs an already upgraded profile in an older version of Firefox. File
   4632  // manager directories without the ".files" suffix then prevent current
   4633  // Firefox from initializing and using the storage.
   4634 
   4635  const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
   4636    QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
   4637        MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection)));
   4638 
   4639    return NS_OK;
   4640  };
   4641 
   4642  return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0,
   4643                               innerFunc);
   4644 }
   4645 
   4646 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
   4647    mozIStorageConnection* aConnection) {
   4648  AssertIsOnIOThread();
   4649  MOZ_ASSERT(aConnection);
   4650 
   4651  // The upgrade is mainly to create a directory padding file in DOM Cache
   4652  // directory to record the overall padding size of an origin.
   4653 
   4654  const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
   4655    QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
   4656        MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection)));
   4657 
   4658    return NS_OK;
   4659  };
   4660 
   4661  return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1,
   4662                               innerFunc);
   4663 }
   4664 
   4665 nsresult QuotaManager::UpgradeStorageFrom2_1To2_2(
   4666    mozIStorageConnection* aConnection) {
   4667  AssertIsOnIOThread();
   4668  MOZ_ASSERT(aConnection);
   4669 
   4670  // The upgrade is mainly to clean obsolete origins in the repositoies, remove
   4671  // asmjs client, and ".tmp" file in the idb folers.
   4672 
   4673  const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
   4674    QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>(
   4675        MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection)));
   4676 
   4677    return NS_OK;
   4678  };
   4679 
   4680  return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2,
   4681                               innerFunc);
   4682 }
   4683 
   4684 nsresult QuotaManager::UpgradeStorageFrom2_2To2_3(
   4685    mozIStorageConnection* aConnection) {
   4686  AssertIsOnIOThread();
   4687  MOZ_ASSERT(aConnection);
   4688 
   4689  const auto innerFunc = [&aConnection](const auto&) -> nsresult {
   4690    // Table `database`
   4691    QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
   4692        nsLiteralCString("CREATE TABLE database"
   4693                         "( cache_version INTEGER NOT NULL DEFAULT 0"
   4694                         ");"))));
   4695 
   4696    QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
   4697        nsLiteralCString("INSERT INTO database (cache_version) "
   4698                         "VALUES (0)"))));
   4699 
   4700 #ifdef DEBUG
   4701    {
   4702      QM_TRY_INSPECT(
   4703          const int32_t& storageVersion,
   4704          MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
   4705 
   4706      MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2));
   4707    }
   4708 #endif
   4709 
   4710    QM_TRY(
   4711        MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3))));
   4712 
   4713    return NS_OK;
   4714  };
   4715 
   4716  return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3,
   4717                               innerFunc);
   4718 }
   4719 
   4720 nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
   4721    nsIFile& aLsArchiveFile) {
   4722  AssertIsOnIOThread();
   4723  MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
   4724 
   4725  QM_TRY_INSPECT(const bool& exists,
   4726                 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
   4727 
   4728  if (!exists) {
   4729    // If the ls archive doesn't exist then ls directories can't exist either.
   4730    return NS_OK;
   4731  }
   4732 
   4733  QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
   4734 
   4735  InvalidateQuotaCache();
   4736 
   4737  // Finally remove the ls archive, so we don't have to check all origin
   4738  // directories next time this method is called.
   4739  QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
   4740 
   4741  return NS_OK;
   4742 }
   4743 
   4744 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
   4745  AssertIsOnIOThread();
   4746 
   4747  QM_TRY_INSPECT(const auto& defaultStorageDir,
   4748                 QM_NewLocalFile(*mDefaultStoragePath));
   4749 
   4750  QM_TRY_INSPECT(const bool& exists,
   4751                 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
   4752 
   4753  if (!exists) {
   4754    return NS_OK;
   4755  }
   4756 
   4757  QM_TRY(CollectEachFile(
   4758      *defaultStorageDir,
   4759      [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
   4760 #ifdef DEBUG
   4761        {
   4762          QM_TRY_INSPECT(const bool& exists,
   4763                         MOZ_TO_RESULT_INVOKE_MEMBER(originDir, Exists));
   4764          MOZ_ASSERT(exists);
   4765        }
   4766 #endif
   4767 
   4768        QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
   4769 
   4770        switch (dirEntryKind) {
   4771          case nsIFileKind::ExistsAsDirectory: {
   4772            QM_TRY_INSPECT(
   4773                const auto& lsDir,
   4774                CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING(
   4775                                                   LS_DIRECTORY_NAME)));
   4776 
   4777            {
   4778              QM_TRY_INSPECT(const bool& exists,
   4779                             MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, Exists));
   4780 
   4781              if (!exists) {
   4782                return Ok{};
   4783              }
   4784            }
   4785 
   4786            {
   4787              QM_TRY_INSPECT(const bool& isDirectory,
   4788                             MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, IsDirectory));
   4789 
   4790              if (!isDirectory) {
   4791                QM_WARNING("ls entry is not a directory!");
   4792 
   4793                return Ok{};
   4794              }
   4795            }
   4796 
   4797            nsString path;
   4798            QM_TRY(MOZ_TO_RESULT(lsDir->GetPath(path)));
   4799 
   4800            QM_WARNING("Deleting %s directory!",
   4801                       NS_ConvertUTF16toUTF8(path).get());
   4802 
   4803            QM_TRY(MOZ_TO_RESULT(lsDir->Remove(/* aRecursive */ true)));
   4804 
   4805            break;
   4806          }
   4807 
   4808          case nsIFileKind::ExistsAsFile: {
   4809            QM_TRY_INSPECT(const auto& leafName,
   4810                           MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   4811                               nsAutoString, originDir, GetLeafName));
   4812 
   4813            // Unknown files during upgrade are allowed. Just warn if we find
   4814            // them.
   4815            if (!IsOSMetadata(leafName)) {
   4816              UNKNOWN_FILE_WARNING(leafName);
   4817            }
   4818 
   4819            break;
   4820          }
   4821 
   4822          case nsIFileKind::DoesNotExist:
   4823            // Ignore files that got removed externally while iterating.
   4824            break;
   4825        }
   4826        return Ok{};
   4827      }));
   4828 
   4829  return NS_OK;
   4830 }
   4831 
   4832 Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
   4833    nsIFile& aLsArchiveFile) const {
   4834  AssertIsOnIOThread();
   4835  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
   4836 
   4837 #ifdef DEBUG
   4838  {
   4839    QM_TRY_INSPECT(const bool& exists,
   4840                   MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
   4841    MOZ_ASSERT(!exists);
   4842  }
   4843 #endif
   4844 
   4845  // Get the storage service first, we will need it at multiple places.
   4846  QM_TRY_INSPECT(const auto& ss,
   4847                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
   4848                                         MOZ_SELECT_OVERLOAD(do_GetService),
   4849                                         MOZ_STORAGE_SERVICE_CONTRACTID));
   4850 
   4851  // Get the web apps store file.
   4852  QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath));
   4853 
   4854  QM_TRY(MOZ_TO_RESULT(
   4855      webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));
   4856 
   4857  // Now check if the web apps store is useable.
   4858  QM_TRY_INSPECT(const auto& connection,
   4859                 CreateWebAppsStoreConnection(*webAppsStoreFile, *ss));
   4860 
   4861  if (connection) {
   4862    // Find out the journal mode.
   4863    QM_TRY_INSPECT(const auto& stmt,
   4864                   CreateAndExecuteSingleStepStatement(
   4865                       *connection, "PRAGMA journal_mode;"_ns));
   4866 
   4867    QM_TRY_INSPECT(const auto& journalMode,
   4868                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt,
   4869                                                     GetUTF8String, 0));
   4870 
   4871    QM_TRY(MOZ_TO_RESULT(stmt->Finalize()));
   4872 
   4873    if (journalMode.EqualsLiteral("wal")) {
   4874      // We don't copy the WAL file, so make sure the old database is fully
   4875      // checkpointed.
   4876      QM_TRY(MOZ_TO_RESULT(
   4877          connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns)));
   4878    }
   4879 
   4880    // Explicitely close the connection before the old database is copied.
   4881    QM_TRY(MOZ_TO_RESULT(connection->Close()));
   4882 
   4883    // Copy the old database. The database is copied from
   4884    // <profile>/webappsstore.sqlite to
   4885    // <profile>/storage/ls-archive-tmp.sqlite
   4886    // We use a "-tmp" postfix since we are not done yet.
   4887    QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
   4888 
   4889    QM_TRY(MOZ_TO_RESULT(webAppsStoreFile->CopyTo(
   4890        storageDir, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
   4891 
   4892    QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
   4893                   GetLocalStorageArchiveTmpFile(*mStoragePath));
   4894 
   4895    if (journalMode.EqualsLiteral("wal")) {
   4896      QM_TRY_INSPECT(
   4897          const auto& lsArchiveTmpConnection,
   4898          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   4899              nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
   4900              lsArchiveTmpFile, mozIStorageService::CONNECTION_DEFAULT));
   4901 
   4902      // The archive will only be used for lazy data migration. There won't be
   4903      // any concurrent readers and writers that could benefit from Write-Ahead
   4904      // Logging. So switch to a standard rollback journal. The standard
   4905      // rollback journal also provides atomicity across multiple attached
   4906      // databases which is import for the lazy data migration to work safely.
   4907      QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->ExecuteSimpleSQL(
   4908          "PRAGMA journal_mode = DELETE;"_ns)));
   4909 
   4910      // Close the connection explicitly. We are going to rename the file below.
   4911      QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->Close()));
   4912    }
   4913 
   4914    // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
   4915    QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile->MoveTo(
   4916        nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME))));
   4917 
   4918    return Ok{};
   4919  }
   4920 
   4921  // If webappsstore database is not useable, just create an empty archive.
   4922  // XXX The code below should be removed and the caller should call us only
   4923  // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
   4924  // reworked to propagate database corruption instead of returning null
   4925  // connection.
   4926  // So, if there's no webappsstore.sqlite
   4927  // MaybeCreateOrUpgradeLocalStorageArchive will call
   4928  // CreateEmptyLocalStorageArchive instead of
   4929  // CopyLocalStorageArchiveFromWebAppsStore.
   4930  // If there's any corruption detected during
   4931  // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
   4932  // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
   4933  // EnsureStorageIsInitializedInternal will fallback to
   4934  // CreateEmptyLocalStorageArchive.
   4935 
   4936  // Ensure the storage directory actually exists.
   4937  QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath));
   4938 
   4939  QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory));
   4940 
   4941  (void)created;
   4942 
   4943  QM_TRY_UNWRAP(auto lsArchiveConnection,
   4944                MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   4945                    nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
   4946                    &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
   4947 
   4948  QM_TRY(MOZ_TO_RESULT(
   4949      StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection)));
   4950 
   4951  return Ok{};
   4952 }
   4953 
   4954 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
   4955 QuotaManager::CreateLocalStorageArchiveConnection(
   4956    nsIFile& aLsArchiveFile) const {
   4957  AssertIsOnIOThread();
   4958  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
   4959 
   4960 #ifdef DEBUG
   4961  {
   4962    QM_TRY_INSPECT(const bool& exists,
   4963                   MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
   4964    MOZ_ASSERT(exists);
   4965  }
   4966 #endif
   4967 
   4968  QM_TRY_INSPECT(const bool& isDirectory,
   4969                 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, IsDirectory));
   4970 
   4971  // A directory with the name of the archive file is treated as corruption
   4972  // (similarly as wrong content of the file).
   4973  QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED));
   4974 
   4975  QM_TRY_INSPECT(const auto& ss,
   4976                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
   4977                                         MOZ_SELECT_OVERLOAD(do_GetService),
   4978                                         MOZ_STORAGE_SERVICE_CONTRACTID));
   4979 
   4980  // This may return NS_ERROR_FILE_CORRUPTED too.
   4981  QM_TRY_UNWRAP(auto connection,
   4982                MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   4983                    nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
   4984                    &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
   4985 
   4986  // The legacy LS implementation removes the database and creates an empty one
   4987  // when the schema can't be updated. The same effect can be achieved here by
   4988  // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
   4989  // sub test case 3 of dom/localstorage/test/unit/test_archive.js
   4990  QM_TRY(
   4991      MOZ_TO_RESULT(StorageDBUpdater::Update(connection))
   4992          .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; }));
   4993 
   4994  return connection;
   4995 }
   4996 
   4997 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
   4998 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
   4999    nsIFile& aLsArchiveFile) {
   5000  AssertIsOnIOThread();
   5001  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
   5002 
   5003  QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
   5004 
   5005 #ifdef DEBUG
   5006  {
   5007    QM_TRY_INSPECT(const bool& exists,
   5008                   MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
   5009 
   5010    MOZ_ASSERT(exists);
   5011  }
   5012 #endif
   5013 
   5014  QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
   5015 
   5016  QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
   5017 
   5018  QM_TRY_UNWRAP(auto connection,
   5019                CreateLocalStorageArchiveConnection(aLsArchiveFile));
   5020 
   5021  QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
   5022 
   5023  return connection;
   5024 }
   5025 
   5026 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
   5027 QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) {
   5028  AssertIsOnIOThread();
   5029  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
   5030 
   5031  QM_TRY_UNWRAP(auto connection,
   5032                RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
   5033 
   5034  QM_TRY(MOZ_TO_RESULT(
   5035      SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
   5036 
   5037  return connection;
   5038 }
   5039 
   5040 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
   5041 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
   5042    nsIFile& aLsArchiveFile) {
   5043  AssertIsOnIOThread();
   5044  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
   5045 
   5046  QM_TRY_UNWRAP(auto connection,
   5047                RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
   5048 
   5049  QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection, 4)));
   5050 
   5051  return connection;
   5052 }
   5053 
   5054 /*
   5055 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
   5056    nsCOMPtr<mozIStorageConnection>& aConnection) {
   5057  AssertIsOnIOThread();
   5058  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
   5059 
   5060  nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
   5061  if (NS_WARN_IF(NS_FAILED(rv))) {
   5062    return rv;
   5063  }
   5064 
   5065  return NS_OK;
   5066 }
   5067 */
   5068 
   5069 #ifdef DEBUG
   5070 
   5071 void QuotaManager::AssertStorageIsInitializedInternal() const {
   5072  AssertIsOnIOThread();
   5073  MOZ_ASSERT(IsStorageInitializedInternal());
   5074 }
   5075 
   5076 #endif  // DEBUG
   5077 
   5078 nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
   5079    nsIFile& aStorageFile) {
   5080  AssertIsOnIOThread();
   5081 
   5082  QM_TRY_INSPECT(const auto& storageFileExists,
   5083                 MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile, Exists));
   5084 
   5085  if (!storageFileExists) {
   5086    QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath));
   5087 
   5088    QM_TRY_INSPECT(const auto& indexedDBDirExists,
   5089                   MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir, Exists));
   5090 
   5091    if (indexedDBDirExists) {
   5092      QM_TRY(MOZ_TO_RESULT(
   5093          UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
   5094              indexedDBDir)));
   5095    }
   5096 
   5097    QM_TRY_INSPECT(const auto& persistentStorageDir,
   5098                   QM_NewLocalFile(*mStoragePath));
   5099 
   5100    QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
   5101        nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
   5102 
   5103    QM_TRY_INSPECT(const auto& persistentStorageDirExists,
   5104                   MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir, Exists));
   5105 
   5106    if (persistentStorageDirExists) {
   5107      QM_TRY(MOZ_TO_RESULT(
   5108          UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
   5109              persistentStorageDir)));
   5110    }
   5111  }
   5112 
   5113  return NS_OK;
   5114 }
   5115 
   5116 nsresult QuotaManager::MaybeCreateOrUpgradeStorage(
   5117    mozIStorageConnection& aConnection) {
   5118  AssertIsOnIOThread();
   5119 
   5120  QM_TRY_UNWRAP(auto storageVersion,
   5121                MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
   5122 
   5123  // Hacky downgrade logic!
   5124  // If we see major.minor of 3.0, downgrade it to be 2.1.
   5125  if (storageVersion == kHackyPreDowngradeStorageVersion) {
   5126    storageVersion = kHackyPostDowngradeStorageVersion;
   5127    QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)),
   5128           QM_PROPAGATE,
   5129           [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
   5130  }
   5131 
   5132  QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
   5133         NS_ERROR_FAILURE, [](const auto&) {
   5134           NS_WARNING("Unable to initialize storage, version is too high!");
   5135         });
   5136 
   5137  if (storageVersion < kStorageVersion) {
   5138    const bool newDatabase = !storageVersion;
   5139 
   5140    QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
   5141 
   5142    QM_TRY_INSPECT(const auto& storageDirExists,
   5143                   MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
   5144 
   5145    const bool newDirectory = !storageDirExists;
   5146 
   5147    if (newDatabase) {
   5148      // Set the page size first.
   5149      if (kSQLitePageSizeOverride) {
   5150        QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsPrintfCString(
   5151            "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
   5152      }
   5153    }
   5154 
   5155    mozStorageTransaction transaction(
   5156        &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
   5157 
   5158    QM_TRY(MOZ_TO_RESULT(transaction.Start()));
   5159 
   5160    // An upgrade method can upgrade the database, the storage or both.
   5161    // The upgrade loop below can only be avoided when there's no database and
   5162    // no storage yet (e.g. new profile).
   5163    if (newDatabase && newDirectory) {
   5164      QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection)));
   5165 
   5166 #ifdef DEBUG
   5167      {
   5168        QM_TRY_INSPECT(
   5169            const int32_t& storageVersion,
   5170            MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion),
   5171            QM_ASSERT_UNREACHABLE);
   5172        MOZ_ASSERT(storageVersion == kStorageVersion);
   5173      }
   5174 #endif
   5175 
   5176      QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
   5177          nsLiteralCString("INSERT INTO database (cache_version) "
   5178                           "VALUES (0)"))));
   5179    } else {
   5180      // This logic needs to change next time we change the storage!
   5181      static_assert(kStorageVersion == int32_t((2 << 16) + 3),
   5182                    "Upgrade function needed due to storage version increase.");
   5183 
   5184      while (storageVersion != kStorageVersion) {
   5185        if (storageVersion == 0) {
   5186          QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection)));
   5187        } else if (storageVersion == MakeStorageVersion(1, 0)) {
   5188          QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection)));
   5189        } else if (storageVersion == MakeStorageVersion(2, 0)) {
   5190          QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection)));
   5191        } else if (storageVersion == MakeStorageVersion(2, 1)) {
   5192          QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection)));
   5193        } else if (storageVersion == MakeStorageVersion(2, 2)) {
   5194          QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection)));
   5195        } else {
   5196          QM_FAIL(NS_ERROR_FAILURE, []() {
   5197            NS_WARNING(
   5198                "Unable to initialize storage, no upgrade path is "
   5199                "available!");
   5200          });
   5201        }
   5202 
   5203        QM_TRY_UNWRAP(storageVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
   5204                                          aConnection, GetSchemaVersion));
   5205      }
   5206 
   5207      MOZ_ASSERT(storageVersion == kStorageVersion);
   5208    }
   5209 
   5210    QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
   5211  }
   5212 
   5213  return NS_OK;
   5214 }
   5215 
   5216 OkOrErr QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
   5217  AssertIsOnIOThread();
   5218 
   5219  QM_TRY_INSPECT(
   5220      const auto& lsArchiveTmpFile,
   5221      QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath)));
   5222 
   5223  QM_TRY_INSPECT(const bool& exists,
   5224                 QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile, Exists));
   5225 
   5226  if (exists) {
   5227    QM_TRY(QM_TO_RESULT(lsArchiveTmpFile->Remove(false)));
   5228  }
   5229 
   5230  return Ok{};
   5231 }
   5232 
   5233 Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
   5234    nsIFile& aLsArchiveFile) {
   5235  AssertIsOnIOThread();
   5236 
   5237  QM_TRY_INSPECT(
   5238      const bool& lsArchiveFileExisted,
   5239      ([this, &aLsArchiveFile]() -> Result<bool, nsresult> {
   5240        QM_TRY_INSPECT(const bool& exists,
   5241                       MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
   5242 
   5243        if (!exists) {
   5244          QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
   5245        }
   5246 
   5247        return exists;
   5248      }()));
   5249 
   5250  QM_TRY_UNWRAP(auto connection,
   5251                CreateLocalStorageArchiveConnection(aLsArchiveFile));
   5252 
   5253  QM_TRY_INSPECT(const auto& initialized,
   5254                 IsLocalStorageArchiveInitialized(*connection));
   5255 
   5256  if (!initialized) {
   5257    QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
   5258  }
   5259 
   5260  QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection));
   5261 
   5262  if (version > kLocalStorageArchiveVersion) {
   5263    // Close local storage archive connection. We are going to remove underlying
   5264    // file.
   5265    QM_TRY(MOZ_TO_RESULT(connection->Close()));
   5266 
   5267    // This will wipe the archive and any migrated data and recopy the archive
   5268    // from webappsstore.sqlite.
   5269    QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile));
   5270 
   5271    QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
   5272 
   5273    MOZ_ASSERT(version == kLocalStorageArchiveVersion);
   5274  } else if (version != kLocalStorageArchiveVersion) {
   5275    // The version can be zero either when the archive didn't exist or it did
   5276    // exist, but the archive was created without any version information.
   5277    // We don't need to do any upgrades only if it didn't exist because existing
   5278    // archives without version information must be recopied to really fix bug
   5279    // 1542104. See also bug 1546305 which introduced archive versions.
   5280    if (!lsArchiveFileExisted) {
   5281      MOZ_ASSERT(version == 0);
   5282 
   5283      QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(
   5284          connection, kLocalStorageArchiveVersion)));
   5285    } else {
   5286      static_assert(kLocalStorageArchiveVersion == 4,
   5287                    "Upgrade function needed due to LocalStorage archive "
   5288                    "version increase.");
   5289 
   5290      while (version != kLocalStorageArchiveVersion) {
   5291        if (version < 4) {
   5292          // Close local storage archive connection. We are going to remove
   5293          // underlying file.
   5294          QM_TRY(MOZ_TO_RESULT(connection->Close()));
   5295 
   5296          // This won't do an "upgrade" in a normal sense. It will wipe the
   5297          // archive and any migrated data and recopy the archive from
   5298          // webappsstore.sqlite
   5299          QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4(
   5300                                        aLsArchiveFile));
   5301        } /* else if (version == 4) {
   5302          QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection)));
   5303        } */
   5304        else {
   5305          QM_FAIL(Err(NS_ERROR_FAILURE), []() {
   5306            QM_WARNING(
   5307                "Unable to initialize LocalStorage archive, no upgrade path "
   5308                "is available!");
   5309          });
   5310        }
   5311 
   5312        QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
   5313      }
   5314 
   5315      MOZ_ASSERT(version == kLocalStorageArchiveVersion);
   5316    }
   5317  }
   5318 
   5319  // At this point, we have finished initializing the local storage archive, and
   5320  // can continue storage initialization. We don't know though if the actual
   5321  // data in the archive file is readable. We can't do a PRAGMA integrity_check
   5322  // here though, because that would be too heavyweight.
   5323 
   5324  return Ok{};
   5325 }
   5326 
   5327 Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive(
   5328    nsIFile& aLsArchiveFile) const {
   5329  AssertIsOnIOThread();
   5330 
   5331  QM_TRY_INSPECT(const bool& exists,
   5332                 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
   5333 
   5334  // If it exists, remove it. It might be a directory, so remove it recursively.
   5335  if (exists) {
   5336    QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(true)));
   5337 
   5338    // XXX If we crash right here, the next session will copy the archive from
   5339    // webappsstore.sqlite again!
   5340    // XXX Create a marker file before removing the archive which can be
   5341    // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
   5342    // archive instead of recopying it from webapppstore.sqlite (in other
   5343    // words, finishing what was started here).
   5344  }
   5345 
   5346  QM_TRY_INSPECT(const auto& ss,
   5347                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
   5348                                         MOZ_SELECT_OVERLOAD(do_GetService),
   5349                                         MOZ_STORAGE_SERVICE_CONTRACTID));
   5350 
   5351  QM_TRY_UNWRAP(const auto connection,
   5352                MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   5353                    nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
   5354                    &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
   5355 
   5356  QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));
   5357 
   5358  QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
   5359 
   5360  QM_TRY(MOZ_TO_RESULT(
   5361      SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
   5362 
   5363  return Ok{};
   5364 }
   5365 
   5366 RefPtr<BoolPromise> QuotaManager::InitializeStorage() {
   5367  AssertIsOnOwningThread();
   5368 
   5369  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   5370      PersistenceScope::CreateFromNull(), OriginScope::FromNull(),
   5371      ClientStorageScope::CreateFromNull(),
   5372      /* aExclusive */ false);
   5373 
   5374  auto prepareInfo = directoryLock->Prepare();
   5375 
   5376  // If storage is initialized but there's a clear storage or shutdown storage
   5377  // operation already scheduled, we can't immediately resolve the promise and
   5378  // return from the function because the clear and shutdown storage operation
   5379  // uninitializes storage.
   5380  if (mStorageInitialized &&
   5381      !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) {
   5382    return BoolPromise::CreateAndResolve(true, __func__);
   5383  }
   5384 
   5385  return directoryLock->Acquire(std::move(prepareInfo))
   5386      ->Then(GetCurrentSerialEventTarget(), __func__,
   5387             [self = RefPtr(this), directoryLock](
   5388                 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   5389               if (aValue.IsReject()) {
   5390                 return BoolPromise::CreateAndReject(aValue.RejectValue(),
   5391                                                     __func__);
   5392               }
   5393 
   5394               return self->InitializeStorage(std::move(directoryLock));
   5395             });
   5396 }
   5397 
   5398 RefPtr<BoolPromise> QuotaManager::InitializeStorage(
   5399    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   5400  AssertIsOnOwningThread();
   5401  MOZ_ASSERT(aDirectoryLock);
   5402  MOZ_ASSERT(aDirectoryLock->Acquired());
   5403 
   5404  // If storage is initialized and the directory lock for the initialize
   5405  // storage operation is acquired, we can immediately resolve the promise and
   5406  // return from the function because there can't be a clear storage or
   5407  // shutdown storage operation which would uninitialize storage.
   5408  if (mStorageInitialized) {
   5409    DropDirectoryLock(aDirectoryLock);
   5410 
   5411    return BoolPromise::CreateAndResolve(true, __func__);
   5412  }
   5413 
   5414  auto initializeStorageOp =
   5415      CreateInitOp(WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
   5416 
   5417  RegisterNormalOriginOp(*initializeStorageOp);
   5418 
   5419  initializeStorageOp->RunImmediately();
   5420 
   5421  return initializeStorageOp->OnResults()->Then(
   5422      GetCurrentSerialEventTarget(), __func__,
   5423      [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
   5424        if (aValue.IsReject()) {
   5425          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   5426        }
   5427 
   5428        self->mStorageInitialized = true;
   5429 
   5430        return BoolPromise::CreateAndResolve(true, __func__);
   5431      });
   5432 }
   5433 
   5434 RefPtr<BoolPromise> QuotaManager::StorageInitialized() {
   5435  AssertIsOnOwningThread();
   5436 
   5437  auto storageInitializedOp =
   5438      CreateStorageInitializedOp(WrapMovingNotNullUnchecked(this));
   5439 
   5440  RegisterNormalOriginOp(*storageInitializedOp);
   5441 
   5442  storageInitializedOp->RunImmediately();
   5443 
   5444  return storageInitializedOp->OnResults();
   5445 }
   5446 
   5447 nsresult QuotaManager::EnsureStorageIsInitializedInternal() {
   5448  GECKO_TRACE_SCOPE("dom::quota",
   5449                    "QuotaManager::EnsureStorageIsInitializedInternal");
   5450 
   5451  DiagnosticAssertIsOnIOThread();
   5452 
   5453  const auto innerFunc =
   5454      [&](const auto& firstInitializationAttempt) -> nsresult {
   5455    if (mStorageConnection) {
   5456      MOZ_ASSERT(firstInitializationAttempt.Recorded());
   5457      return NS_OK;
   5458    }
   5459 
   5460    QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath));
   5461    QM_TRY(MOZ_TO_RESULT(storageFile->Append(mStorageName + kSQLiteSuffix)));
   5462 
   5463    QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile)));
   5464 
   5465    QM_TRY_INSPECT(const auto& ss,
   5466                   MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
   5467                                           MOZ_SELECT_OVERLOAD(do_GetService),
   5468                                           MOZ_STORAGE_SERVICE_CONTRACTID));
   5469 
   5470    QM_TRY_UNWRAP(
   5471        auto connection,
   5472        QM_OR_ELSE_WARN_IF(
   5473            // Expression.
   5474            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   5475                nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
   5476                storageFile, mozIStorageService::CONNECTION_DEFAULT),
   5477            // Predicate.
   5478            IsDatabaseCorruptionError,
   5479            // Fallback.
   5480            ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
   5481 
   5482    if (!connection) {
   5483      // Nuke the database file.
   5484      QM_TRY(MOZ_TO_RESULT(storageFile->Remove(false)));
   5485 
   5486      QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   5487                                    nsCOMPtr<mozIStorageConnection>, ss,
   5488                                    OpenUnsharedDatabase, storageFile,
   5489                                    mozIStorageService::CONNECTION_DEFAULT));
   5490    }
   5491 
   5492    // We want extra durability for this important file.
   5493    QM_TRY(MOZ_TO_RESULT(
   5494        connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns)));
   5495 
   5496    // Check to make sure that the storage version is correct.
   5497    QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection)));
   5498 
   5499    QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
   5500 
   5501    QM_TRY_INSPECT(const auto& lsArchiveFile,
   5502                   GetLocalStorageArchiveFile(*mStoragePath));
   5503 
   5504    if (CachedNextGenLocalStorageEnabled()) {
   5505      QM_TRY(QM_OR_ELSE_WARN_IF(
   5506          // Expression.
   5507          MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile),
   5508          // Predicate.
   5509          IsDatabaseCorruptionError,
   5510          // Fallback.
   5511          ([&](const nsresult rv) -> Result<Ok, nsresult> {
   5512            QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile));
   5513          })));
   5514    } else {
   5515      QM_TRY(
   5516          MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile)));
   5517    }
   5518 
   5519    QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection));
   5520 
   5521    if (mCacheUsable && gInvalidateQuotaCache) {
   5522      QM_TRY(InvalidateCache(*connection));
   5523 
   5524      gInvalidateQuotaCache = false;
   5525    }
   5526 
   5527    uint32_t pauseOnIOThreadMs =
   5528        StaticPrefs::dom_quotaManager_storageInitialization_pauseOnIOThreadMs();
   5529    if (pauseOnIOThreadMs > 0) {
   5530      PR_Sleep(PR_MillisecondsToInterval(pauseOnIOThreadMs));
   5531    }
   5532 
   5533    mStorageConnection = std::move(connection);
   5534 
   5535    return NS_OK;
   5536  };
   5537 
   5538  return ExecuteInitialization(
   5539      Initialization::Storage,
   5540      "dom::quota::FirstInitializationAttempt::Storage"_ns, innerFunc);
   5541 }
   5542 
   5543 RefPtr<BoolPromise> QuotaManager::TemporaryStorageInitialized() {
   5544  AssertIsOnOwningThread();
   5545 
   5546  auto temporaryStorageInitializedOp =
   5547      CreateTemporaryStorageInitializedOp(WrapMovingNotNullUnchecked(this));
   5548 
   5549  RegisterNormalOriginOp(*temporaryStorageInitializedOp);
   5550 
   5551  temporaryStorageInitializedOp->RunImmediately();
   5552 
   5553  return temporaryStorageInitializedOp->OnResults();
   5554 }
   5555 
   5556 RefPtr<UniversalDirectoryLockPromise> QuotaManager::OpenStorageDirectory(
   5557    const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope,
   5558    const ClientStorageScope& aClientStorageScope, bool aExclusive,
   5559    bool aInitializeOrigins, DirectoryLockCategory aCategory,
   5560    Maybe<RefPtr<UniversalDirectoryLock>&> aPendingDirectoryLockOut) {
   5561  AssertIsOnOwningThread();
   5562 
   5563  nsTArray<RefPtr<BoolPromise>> promises;
   5564 
   5565  // Directory locks for specific initializations can be null, indicating that
   5566  // either the initialization should not occur or it has already been
   5567  // completed.
   5568 
   5569  RefPtr<UniversalDirectoryLock> storageDirectoryLock =
   5570      CreateDirectoryLockForInitialization(
   5571          *this, PersistenceScope::CreateFromNull(), OriginScope::FromNull(),
   5572          ClientStorageScope::CreateFromNull(), mStorageInitialized,
   5573          MakeBlockedByChecker(kUninitStorageOnlyCategory),
   5574          MakeBackInserter(promises));
   5575 
   5576  RefPtr<UniversalDirectoryLock> persistentStorageDirectoryLock;
   5577 
   5578  RefPtr<UniversalDirectoryLock> temporaryStorageDirectoryLock;
   5579 
   5580  if (aInitializeOrigins) {
   5581    if (MatchesPersistentPersistenceScope(aPersistenceScope)) {
   5582      persistentStorageDirectoryLock = CreateDirectoryLockForInitialization(
   5583          *this, PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT),
   5584          OriginScope::FromNull(), ClientStorageScope::CreateFromNull(),
   5585          mPersistentStorageInitialized,
   5586          MakeBlockedByChecker(kUninitStorageOnlyCategory),
   5587          MakeBackInserter(promises));
   5588    }
   5589 
   5590    // We match all best effort persistence types, but the persistence scope is
   5591    // created only for the temporary and default persistence type because the
   5592    // repository for the private persistence type is never initialized as part
   5593    // of temporary initialization. However, some other steps of the temporary
   5594    // storage initialization need to be done even for the private persistence
   5595    // type. For example, the initialization of mTemporaryStorageLimit.
   5596    if (MatchesBestEffortPersistenceScope(aPersistenceScope)) {
   5597      temporaryStorageDirectoryLock = CreateDirectoryLockForInitialization(
   5598          *this,
   5599          PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
   5600                                          PERSISTENCE_TYPE_DEFAULT),
   5601          OriginScope::FromNull(), ClientStorageScope::CreateFromNull(),
   5602          mTemporaryStorageInitialized,
   5603          MakeBlockedByChecker(kUninitStorageOnlyCategory),
   5604          MakeBackInserter(promises));
   5605    }
   5606  }
   5607 
   5608  RefPtr<UniversalDirectoryLock> universalDirectoryLock =
   5609      CreateDirectoryLockInternal(aPersistenceScope, aOriginScope,
   5610                                  aClientStorageScope, aExclusive, aCategory);
   5611 
   5612  RefPtr<BoolPromise> universalDirectoryLockPromise =
   5613      universalDirectoryLock->Acquire();
   5614 
   5615  if (aPendingDirectoryLockOut.isSome()) {
   5616    aPendingDirectoryLockOut.ref() = universalDirectoryLock;
   5617  }
   5618 
   5619  return BoolPromise::All(GetCurrentSerialEventTarget(), promises)
   5620      ->Then(
   5621          GetCurrentSerialEventTarget(), __func__,
   5622          [](const CopyableTArray<bool>& aResolveValues) {
   5623            return BoolPromise::CreateAndResolve(true, __func__);
   5624          },
   5625          [](nsresult aRejectValue) {
   5626            return BoolPromise::CreateAndReject(aRejectValue, __func__);
   5627          })
   5628      ->Then(GetCurrentSerialEventTarget(), __func__,
   5629             MaybeInitialize(std::move(storageDirectoryLock), this,
   5630                             &QuotaManager::InitializeStorage))
   5631      ->Then(GetCurrentSerialEventTarget(), __func__,
   5632             MaybeInitialize(std::move(persistentStorageDirectoryLock), this,
   5633                             &QuotaManager::InitializePersistentStorage))
   5634      ->Then(GetCurrentSerialEventTarget(), __func__,
   5635             MaybeInitialize(std::move(temporaryStorageDirectoryLock), this,
   5636                             &QuotaManager::InitializeTemporaryStorage))
   5637      ->Then(GetCurrentSerialEventTarget(), __func__,
   5638             [universalDirectoryLockPromise =
   5639                  std::move(universalDirectoryLockPromise)](
   5640                 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   5641               if (aValue.IsReject()) {
   5642                 return BoolPromise::CreateAndReject(aValue.RejectValue(),
   5643                                                     __func__);
   5644               }
   5645 
   5646               return std::move(universalDirectoryLockPromise);
   5647             })
   5648      ->Then(GetCurrentSerialEventTarget(), __func__,
   5649             [universalDirectoryLock = std::move(universalDirectoryLock)](
   5650                 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   5651               if (aValue.IsReject()) {
   5652                 DropDirectoryLockIfNotDropped(universalDirectoryLock);
   5653 
   5654                 return UniversalDirectoryLockPromise::CreateAndReject(
   5655                     aValue.RejectValue(), __func__);
   5656               }
   5657 
   5658               return UniversalDirectoryLockPromise::CreateAndResolve(
   5659                   std::move(universalDirectoryLock), __func__);
   5660             });
   5661 }
   5662 
   5663 RefPtr<QuotaManager::ClientDirectoryLockHandlePromise>
   5664 QuotaManager::OpenClientDirectory(
   5665    const ClientMetadata& aClientMetadata, bool aInitializeOrigin,
   5666    bool aCreateIfNonExistent,
   5667    Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) {
   5668  AssertIsOnOwningThread();
   5669 
   5670  RefPtr<ClientDirectoryLockHandlePromise> promise =
   5671      OpenClientDirectoryImpl(aClientMetadata, aInitializeOrigin,
   5672                              aCreateIfNonExistent, aPendingDirectoryLockOut);
   5673 
   5674  NotifyClientDirectoryOpeningStarted(*this);
   5675 
   5676  return promise;
   5677 }
   5678 
   5679 RefPtr<QuotaManager::ClientDirectoryLockHandlePromise>
   5680 QuotaManager::OpenClientDirectoryImpl(
   5681    const ClientMetadata& aClientMetadata, bool aInitializeOrigin,
   5682    bool aCreateIfNonExistent,
   5683    Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) {
   5684  AssertIsOnOwningThread();
   5685 
   5686  const auto persistenceType = aClientMetadata.mPersistenceType;
   5687 
   5688  nsTArray<RefPtr<BoolPromise>> promises;
   5689 
   5690  // Directory locks for specific initializations can be null, indicating that
   5691  // either the initialization should not occur or it has already been
   5692  // completed.
   5693 
   5694  RefPtr<UniversalDirectoryLock> storageDirectoryLock =
   5695      CreateDirectoryLockForInitialization(
   5696          *this, PersistenceScope::CreateFromNull(), OriginScope::FromNull(),
   5697          ClientStorageScope::CreateFromNull(), mStorageInitialized,
   5698          MakeBlockedByChecker(kUninitStorageOnlyCategory),
   5699          MakeBackInserter(promises));
   5700 
   5701  RefPtr<UniversalDirectoryLock> temporaryStorageDirectoryLock;
   5702 
   5703  RefPtr<UniversalDirectoryLock> groupDirectoryLock;
   5704 
   5705  if (IsBestEffortPersistenceType(persistenceType)) {
   5706    temporaryStorageDirectoryLock = CreateDirectoryLockForInitialization(
   5707        *this,
   5708        PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
   5709                                        PERSISTENCE_TYPE_DEFAULT),
   5710        OriginScope::FromNull(), ClientStorageScope::CreateFromNull(),
   5711        mTemporaryStorageInitialized,
   5712        MakeBlockedByChecker(kUninitStorageOnlyCategory),
   5713        MakeBackInserter(promises));
   5714 
   5715    const bool groupInitialized = IsTemporaryGroupInitialized(aClientMetadata);
   5716 
   5717    groupDirectoryLock = CreateDirectoryLockForInitialization(
   5718        *this,
   5719        PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
   5720                                        PERSISTENCE_TYPE_DEFAULT),
   5721        OriginScope::FromGroup(aClientMetadata.mGroup),
   5722        ClientStorageScope::CreateFromNull(), groupInitialized,
   5723        MakeBlockedByChecker(kUninitStorageOnlyCategory),
   5724        MakeBackInserter(promises));
   5725  }
   5726 
   5727  RefPtr<UniversalDirectoryLock> originDirectoryLock;
   5728 
   5729  if (aInitializeOrigin) {
   5730    const bool originInitialized =
   5731        persistenceType == PERSISTENCE_TYPE_PERSISTENT
   5732            ? IsPersistentOriginInitialized(aClientMetadata)
   5733            : IsTemporaryOriginInitialized(aClientMetadata);
   5734 
   5735    originDirectoryLock = CreateDirectoryLockForInitialization(
   5736        *this, PersistenceScope::CreateFromValue(persistenceType),
   5737        OriginScope::FromOrigin(aClientMetadata),
   5738        ClientStorageScope::CreateFromNull(), originInitialized,
   5739        MakeBlockedByChecker(kUninitOriginsAndBroaderCategories),
   5740        MakeBackInserter(promises));
   5741  }
   5742 
   5743  RefPtr<UniversalDirectoryLock> clientInitDirectoryLock;
   5744 
   5745  const bool clientInitialized =
   5746      persistenceType == PERSISTENCE_TYPE_PERSISTENT
   5747          ? IsPersistentClientInitialized(aClientMetadata)
   5748          : IsTemporaryClientInitialized(aClientMetadata);
   5749 
   5750  clientInitDirectoryLock = CreateDirectoryLockForInitialization(
   5751      *this, PersistenceScope::CreateFromValue(persistenceType),
   5752      OriginScope::FromOrigin(aClientMetadata),
   5753      ClientStorageScope::CreateFromClient(aClientMetadata.mClientType),
   5754      clientInitialized,
   5755      MakeBlockedByChecker(kUninitClientsAndBroaderCategories),
   5756      MakeBackInserter(promises));
   5757 
   5758  RefPtr<ClientDirectoryLock> clientDirectoryLock =
   5759      CreateDirectoryLock(aClientMetadata, /* aExclusive */ false);
   5760 
   5761  promises.AppendElement(clientDirectoryLock->Acquire());
   5762 
   5763  if (aPendingDirectoryLockOut.isSome()) {
   5764    aPendingDirectoryLockOut.ref() = clientDirectoryLock;
   5765  }
   5766 
   5767  RefPtr<UniversalDirectoryLock> firstAccessDirectoryLock =
   5768      CreateSaveOriginAccessTimeLock(*this, aClientMetadata);
   5769 
   5770  promises.AppendElement(firstAccessDirectoryLock->Acquire());
   5771 
   5772  RefPtr<UniversalDirectoryLock> lastAccessDirectoryLock =
   5773      CreateSaveOriginAccessTimeLock(*this, aClientMetadata);
   5774 
   5775  promises.AppendElement(lastAccessDirectoryLock->Acquire());
   5776 
   5777  return BoolPromise::All(GetCurrentSerialEventTarget(), promises)
   5778      ->Then(
   5779          GetCurrentSerialEventTarget(), __func__,
   5780          [](const CopyableTArray<bool>& aResolveValues) {
   5781            return BoolPromise::CreateAndResolve(true, __func__);
   5782          },
   5783          [](nsresult aRejectValue) {
   5784            return BoolPromise::CreateAndReject(aRejectValue, __func__);
   5785          })
   5786      ->Then(GetCurrentSerialEventTarget(), __func__,
   5787             MaybeInitialize(std::move(storageDirectoryLock), this,
   5788                             &QuotaManager::InitializeStorage))
   5789      ->Then(GetCurrentSerialEventTarget(), __func__,
   5790             MaybeInitialize(std::move(temporaryStorageDirectoryLock), this,
   5791                             &QuotaManager::InitializeTemporaryStorage))
   5792      ->Then(
   5793          GetCurrentSerialEventTarget(), __func__,
   5794          MaybeInitialize(
   5795              std::move(groupDirectoryLock),
   5796              [self = RefPtr(this), aClientMetadata](
   5797                  RefPtr<UniversalDirectoryLock> groupDirectoryLock) mutable {
   5798                return self->InitializeTemporaryGroup(
   5799                    aClientMetadata, std::move(groupDirectoryLock));
   5800              }))
   5801      ->Then(
   5802          GetCurrentSerialEventTarget(), __func__,
   5803          MaybeInitialize(
   5804              std::move(originDirectoryLock),
   5805              [self = RefPtr(this), aClientMetadata, aCreateIfNonExistent](
   5806                  RefPtr<UniversalDirectoryLock> originDirectoryLock) mutable {
   5807                if (aClientMetadata.mPersistenceType ==
   5808                    PERSISTENCE_TYPE_PERSISTENT) {
   5809                  return self->InitializePersistentOrigin(
   5810                      aClientMetadata, std::move(originDirectoryLock));
   5811                }
   5812 
   5813                return self->InitializeTemporaryOrigin(
   5814                    aClientMetadata, aCreateIfNonExistent,
   5815                    std::move(originDirectoryLock));
   5816              }))
   5817      ->Then(GetCurrentSerialEventTarget(), __func__,
   5818             MaybeInitialize(
   5819                 std::move(clientInitDirectoryLock),
   5820                 [self = RefPtr(this), aClientMetadata,
   5821                  aCreateIfNonExistent](RefPtr<UniversalDirectoryLock>
   5822                                            clientInitDirectoryLock) mutable {
   5823                   if (aClientMetadata.mPersistenceType ==
   5824                       PERSISTENCE_TYPE_PERSISTENT) {
   5825                     return self->InitializePersistentClient(
   5826                         aClientMetadata, std::move(clientInitDirectoryLock));
   5827                   }
   5828 
   5829                   return self->InitializeTemporaryClient(
   5830                       aClientMetadata, aCreateIfNonExistent,
   5831                       std::move(clientInitDirectoryLock));
   5832                 }))
   5833      ->Then(
   5834          GetCurrentSerialEventTarget(), __func__ /* clang formatting anchor */,
   5835          MaybeFinalize(
   5836              std::move(clientDirectoryLock),
   5837              std::move(firstAccessDirectoryLock),
   5838              std::move(lastAccessDirectoryLock),
   5839              [self = RefPtr(this), aClientMetadata](
   5840                  RefPtr<ClientDirectoryLock> clientDirectoryLock,
   5841                  RefPtr<UniversalDirectoryLock> firstAccessDirectoryLock,
   5842                  RefPtr<UniversalDirectoryLock>
   5843                      lastAccessDirectoryLock) mutable {
   5844                auto clientDirectoryLockHandle =
   5845                    ClientDirectoryLockHandle(std::move(clientDirectoryLock));
   5846 
   5847                self->RegisterClientDirectoryLockHandle(
   5848                    aClientMetadata,
   5849                    [&self = *self, &aClientMetadata, &firstAccessDirectoryLock,
   5850                     &lastAccessDirectoryLock](
   5851                        OpenClientDirectoryInfo&
   5852                            aOpenClientDirectoryInfo) mutable {
   5853                      if (aClientMetadata.mPersistenceType !=
   5854                          PERSISTENCE_TYPE_PERSISTENT) {
   5855                        aOpenClientDirectoryInfo.SetFirstAccessPromise(
   5856                            self.SaveOriginAccessTime(
   5857                                aClientMetadata,
   5858                                std::move(firstAccessDirectoryLock)));
   5859 
   5860                        aOpenClientDirectoryInfo.SetLastAccessDirectoryLock(
   5861                            std::move(lastAccessDirectoryLock));
   5862                      }
   5863                    });
   5864 
   5865                clientDirectoryLockHandle.SetRegistered(true);
   5866 
   5867                SafeDropDirectoryLock(firstAccessDirectoryLock);
   5868                SafeDropDirectoryLock(lastAccessDirectoryLock);
   5869 
   5870                return Map<ClientDirectoryLockHandlePromise>(
   5871                    self->WithOpenClientDirectoryInfo(
   5872                        aClientMetadata,
   5873                        [aClientMetadata](
   5874                            OpenClientDirectoryInfo& aOpenClientDirectoryInfo) {
   5875                          if (aClientMetadata.mPersistenceType !=
   5876                              PERSISTENCE_TYPE_PERSISTENT) {
   5877                            return aOpenClientDirectoryInfo
   5878                                .AcquireFirstAccessPromise();
   5879                          }
   5880 
   5881                          return BoolPromise::CreateAndResolve(true, __func__);
   5882                        }),
   5883                    [clientDirectoryLockHandle =
   5884                         std::move(clientDirectoryLockHandle)](
   5885                        const BoolPromise::ResolveOrRejectValue&
   5886                            aValue) mutable {
   5887                      return std::move(clientDirectoryLockHandle);
   5888                    });
   5889              }));
   5890 }
   5891 
   5892 RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
   5893    const ClientMetadata& aClientMetadata, bool aExclusive) {
   5894  AssertIsOnOwningThread();
   5895 
   5896  return ClientDirectoryLock::Create(
   5897      WrapNotNullUnchecked(this), aClientMetadata.mPersistenceType,
   5898      aClientMetadata, aClientMetadata.mClientType, aExclusive);
   5899 }
   5900 
   5901 RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
   5902    const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope,
   5903    const ClientStorageScope& aClientStorageScope, bool aExclusive,
   5904    DirectoryLockCategory aCategory) {
   5905  AssertIsOnOwningThread();
   5906 
   5907  return UniversalDirectoryLock::CreateInternal(
   5908      WrapNotNullUnchecked(this), aPersistenceScope, aOriginScope,
   5909      aClientStorageScope, aExclusive, aCategory);
   5910 }
   5911 
   5912 bool QuotaManager::IsPendingOrigin(
   5913    const OriginMetadata& aOriginMetadata) const {
   5914  MutexAutoLock lock(mQuotaMutex);
   5915 
   5916  RefPtr<OriginInfo> originInfo =
   5917      LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
   5918 
   5919  return originInfo && !originInfo->LockedDirectoryExists();
   5920 }
   5921 
   5922 RefPtr<BoolPromise> QuotaManager::InitializePersistentStorage() {
   5923  AssertIsOnOwningThread();
   5924 
   5925  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   5926      PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT),
   5927      OriginScope::FromNull(), ClientStorageScope::CreateFromNull(),
   5928      /* aExclusive */ false);
   5929 
   5930  auto prepareInfo = directoryLock->Prepare();
   5931 
   5932  // If persistent storage is initialized but there's a clear storage or
   5933  // shutdown storage operation already scheduled, we can't immediately resolve
   5934  // the promise and return from the function because the clear or shutdown
   5935  // storage operation uninitializes storage.
   5936  if (mPersistentStorageInitialized &&
   5937      !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) {
   5938    return BoolPromise::CreateAndResolve(true, __func__);
   5939  }
   5940 
   5941  return directoryLock->Acquire(std::move(prepareInfo))
   5942      ->Then(
   5943          GetCurrentSerialEventTarget(), __func__,
   5944          [self = RefPtr(this), directoryLock](
   5945              const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   5946            if (aValue.IsReject()) {
   5947              return BoolPromise::CreateAndReject(aValue.RejectValue(),
   5948                                                  __func__);
   5949            }
   5950 
   5951            return self->InitializePersistentStorage(std::move(directoryLock));
   5952          });
   5953 }
   5954 
   5955 RefPtr<BoolPromise> QuotaManager::InitializePersistentStorage(
   5956    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   5957  AssertIsOnOwningThread();
   5958  MOZ_ASSERT(aDirectoryLock);
   5959  MOZ_ASSERT(aDirectoryLock->Acquired());
   5960 
   5961  // If persistent storage is initialized and the directory lock for the
   5962  // initialize persistent storage operation is acquired, we can immediately
   5963  // resolve the promise and return from the function because there can't be a
   5964  // clear storage or shutdown storage operation which would uninitialize
   5965  // persistent storage.
   5966  if (mPersistentStorageInitialized) {
   5967    DropDirectoryLock(aDirectoryLock);
   5968 
   5969    return BoolPromise::CreateAndResolve(true, __func__);
   5970  }
   5971 
   5972  auto initializePersistentStorageOp = CreateInitializePersistentStorageOp(
   5973      WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
   5974 
   5975  RegisterNormalOriginOp(*initializePersistentStorageOp);
   5976 
   5977  initializePersistentStorageOp->RunImmediately();
   5978 
   5979  return initializePersistentStorageOp->OnResults()->Then(
   5980      GetCurrentSerialEventTarget(), __func__,
   5981      [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
   5982        if (aValue.IsReject()) {
   5983          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   5984        }
   5985 
   5986        self->mPersistentStorageInitialized = true;
   5987 
   5988        return BoolPromise::CreateAndResolve(true, __func__);
   5989      });
   5990 }
   5991 
   5992 RefPtr<BoolPromise> QuotaManager::PersistentStorageInitialized() {
   5993  AssertIsOnOwningThread();
   5994 
   5995  auto persistentStorageInitializedOp =
   5996      CreatePersistentStorageInitializedOp(WrapMovingNotNullUnchecked(this));
   5997 
   5998  RegisterNormalOriginOp(*persistentStorageInitializedOp);
   5999 
   6000  persistentStorageInitializedOp->RunImmediately();
   6001 
   6002  return persistentStorageInitializedOp->OnResults();
   6003 }
   6004 
   6005 nsresult QuotaManager::EnsurePersistentStorageIsInitializedInternal() {
   6006  AssertIsOnIOThread();
   6007  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   6008 
   6009  const auto innerFunc =
   6010      [&](const auto& firstInitializationAttempt) -> nsresult {
   6011    if (mPersistentStorageInitializedInternal) {
   6012      MOZ_ASSERT(firstInitializationAttempt.Recorded());
   6013      return NS_OK;
   6014    }
   6015 
   6016    QM_TRY(MOZ_TO_RESULT(ExecuteInitialization(
   6017        Initialization::PersistentRepository, [&](const auto&) -> nsresult {
   6018          return InitializeRepository(PERSISTENCE_TYPE_PERSISTENT,
   6019                                      [](auto&) {});
   6020        })));
   6021 
   6022    mPersistentStorageInitializedInternal = true;
   6023 
   6024    return NS_OK;
   6025  };
   6026 
   6027  return ExecuteInitialization(
   6028      Initialization::TemporaryStorage,
   6029      "dom::quota::FirstInitializationAttempt::PersistentStorage"_ns,
   6030      innerFunc);
   6031 }
   6032 
   6033 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryGroup(
   6034    const PrincipalMetadata& aPrincipalMetadata) {
   6035  AssertIsOnOwningThread();
   6036 
   6037  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   6038      PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
   6039                                      PERSISTENCE_TYPE_DEFAULT),
   6040      OriginScope::FromGroup(aPrincipalMetadata.mGroup),
   6041      ClientStorageScope::CreateFromNull(),
   6042      /* aExclusive */ false);
   6043 
   6044  auto prepareInfo = directoryLock->Prepare();
   6045 
   6046  // If temporary group is initialized but there's a clear storage or shutdown
   6047  // storage operation already scheduled, we can't immediately resolve the
   6048  // promise and return from the function because the clear and shutdown
   6049  // storage operation uninitializes storage.
   6050  if (IsTemporaryGroupInitialized(aPrincipalMetadata) &&
   6051      !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) {
   6052    return BoolPromise::CreateAndResolve(true, __func__);
   6053  }
   6054 
   6055  return directoryLock->Acquire(std::move(prepareInfo))
   6056      ->Then(GetCurrentSerialEventTarget(), __func__,
   6057             [self = RefPtr(this), aPrincipalMetadata, directoryLock](
   6058                 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   6059               if (aValue.IsReject()) {
   6060                 return BoolPromise::CreateAndReject(aValue.RejectValue(),
   6061                                                     __func__);
   6062               }
   6063 
   6064               return self->InitializeTemporaryGroup(aPrincipalMetadata,
   6065                                                     std::move(directoryLock));
   6066             });
   6067 }
   6068 
   6069 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryGroup(
   6070    const PrincipalMetadata& aPrincipalMetadata,
   6071    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   6072  AssertIsOnOwningThread();
   6073  MOZ_ASSERT(aDirectoryLock);
   6074  MOZ_ASSERT(aDirectoryLock->Acquired());
   6075 
   6076  // If temporary group is initialized and the directory lock for the
   6077  // initialize temporary group operation is acquired, we can immediately
   6078  // resolve the promise and return from the function because there can't be a
   6079  // clear storage or shutdown storage operation which would uninitialize
   6080  // temporary storage.
   6081  if (IsTemporaryGroupInitialized(aPrincipalMetadata)) {
   6082    DropDirectoryLock(aDirectoryLock);
   6083 
   6084    return BoolPromise::CreateAndResolve(true, __func__);
   6085  }
   6086 
   6087  auto initializeTemporaryGroupOp = CreateInitializeTemporaryGroupOp(
   6088      WrapMovingNotNullUnchecked(this), aPrincipalMetadata,
   6089      std::move(aDirectoryLock));
   6090 
   6091  RegisterNormalOriginOp(*initializeTemporaryGroupOp);
   6092 
   6093  initializeTemporaryGroupOp->RunImmediately();
   6094 
   6095  return Map<BoolPromise>(
   6096      initializeTemporaryGroupOp->OnResults(),
   6097      [self = RefPtr(this), group = aPrincipalMetadata.mGroup](
   6098          const BoolPromise::ResolveOrRejectValue& aValue) {
   6099        self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Insert(
   6100            group);
   6101 
   6102        return aValue.ResolveValue();
   6103      });
   6104 }
   6105 
   6106 RefPtr<BoolPromise> QuotaManager::TemporaryGroupInitialized(
   6107    const PrincipalMetadata& aPrincipalMetadata) {
   6108  AssertIsOnOwningThread();
   6109 
   6110  auto temporaryGroupInitializedOp = CreateTemporaryGroupInitializedOp(
   6111      WrapMovingNotNullUnchecked(this), aPrincipalMetadata);
   6112 
   6113  RegisterNormalOriginOp(*temporaryGroupInitializedOp);
   6114 
   6115  temporaryGroupInitializedOp->RunImmediately();
   6116 
   6117  return temporaryGroupInitializedOp->OnResults();
   6118 }
   6119 
   6120 bool QuotaManager::IsTemporaryGroupInitialized(
   6121    const PrincipalMetadata& aPrincipalMetadata) {
   6122  AssertIsOnOwningThread();
   6123 
   6124  return mBackgroundThreadAccessible.Access()->mInitializedGroups.Contains(
   6125      aPrincipalMetadata.mGroup);
   6126 }
   6127 
   6128 bool QuotaManager::IsTemporaryGroupInitializedInternal(
   6129    const PrincipalMetadata& aPrincipalMetadata) const {
   6130  AssertIsOnIOThread();
   6131 
   6132  MutexAutoLock lock(mQuotaMutex);
   6133 
   6134  return LockedHasGroupInfoPair(aPrincipalMetadata.mGroup);
   6135 }
   6136 
   6137 Result<Ok, nsresult> QuotaManager::EnsureTemporaryGroupIsInitializedInternal(
   6138    const PrincipalMetadata& aPrincipalMetadata) {
   6139  AssertIsOnIOThread();
   6140  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   6141  MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
   6142 
   6143  const auto innerFunc = [&aPrincipalMetadata,
   6144                          this](const auto&) -> mozilla::Result<Ok, nsresult> {
   6145    NotifyGroupInitializationStarted(*this);
   6146 
   6147    const auto& array =
   6148        mIOThreadAccessible.Access()->mAllTemporaryOrigins.Lookup(
   6149            aPrincipalMetadata.mGroup);
   6150    if (!array) {
   6151      return Ok{};
   6152    }
   6153 
   6154    // XXX At the moment, the loop skips all elements in the array because
   6155    // temporary storage initialization still initializes all temporary
   6156    // origins. This is going to change soon with the planned asynchronous
   6157    // temporary origin initialization done in the background.
   6158    for (const auto& originMetadata : *array) {
   6159      if (NS_WARN_IF(IsShuttingDown())) {
   6160        return Err(NS_ERROR_ABORT);
   6161      }
   6162 
   6163      if (IsTemporaryOriginInitializedInternal(originMetadata)) {
   6164        continue;
   6165      }
   6166 
   6167      QM_TRY_UNWRAP(auto directory, GetOriginDirectory(originMetadata));
   6168 
   6169      QM_TRY_INSPECT(const auto& metadata,
   6170                     LoadFullOriginMetadataWithRestore(directory));
   6171 
   6172      // XXX Check corruption here!
   6173      QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, metadata,
   6174                                            /* aForGroup */ true)));
   6175    }
   6176 
   6177    // XXX Evict origins that exceed their group limit here.
   6178 
   6179    SleepIfEnabled(
   6180        StaticPrefs::dom_quotaManager_groupInitialization_pauseOnIOThreadMs());
   6181 
   6182    return Ok{};
   6183  };
   6184 
   6185  return ExecuteGroupInitialization(
   6186      aPrincipalMetadata.mGroup, GroupInitialization::TemporaryGroup,
   6187      "dom::quota::FirstOriginInitializationAttempt::TemporaryGroup"_ns,
   6188      innerFunc);
   6189 }
   6190 
   6191 RefPtr<BoolPromise> QuotaManager::InitializePersistentOrigin(
   6192    const OriginMetadata& aOriginMetadata) {
   6193  AssertIsOnOwningThread();
   6194  MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6195 
   6196  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   6197      PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT),
   6198      OriginScope::FromOrigin(aOriginMetadata),
   6199      ClientStorageScope::CreateFromNull(),
   6200      /* aExclusive */ false);
   6201 
   6202  auto prepareInfo = directoryLock->Prepare();
   6203 
   6204  // If persistent origin is initialized but there's a clear storage, shutdown
   6205  // storage, clear origin, or shutdown origin operation already scheduled, we
   6206  // can't immediately resolve the promise and return from the function because
   6207  // the clear and shutdown storage operations uninitialize storage (which also
   6208  // includes uninitialization of origins) and because clear and shutdown origin
   6209  // operations uninitialize origins directly.
   6210  if (IsPersistentOriginInitialized(aOriginMetadata) &&
   6211      !prepareInfo.IsBlockedBy(kUninitOriginsAndBroaderCategories)) {
   6212    return BoolPromise::CreateAndResolve(true, __func__);
   6213  }
   6214 
   6215  return directoryLock->Acquire(std::move(prepareInfo))
   6216      ->Then(GetCurrentSerialEventTarget(), __func__,
   6217             [self = RefPtr(this), aOriginMetadata, directoryLock](
   6218                 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   6219               if (aValue.IsReject()) {
   6220                 return BoolPromise::CreateAndReject(aValue.RejectValue(),
   6221                                                     __func__);
   6222               }
   6223 
   6224               return self->InitializePersistentOrigin(
   6225                   aOriginMetadata, std::move(directoryLock));
   6226             });
   6227 }
   6228 
   6229 RefPtr<BoolPromise> QuotaManager::InitializePersistentOrigin(
   6230    const OriginMetadata& aOriginMetadata,
   6231    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   6232  AssertIsOnOwningThread();
   6233  MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6234  MOZ_ASSERT(aDirectoryLock);
   6235  MOZ_ASSERT(aDirectoryLock->Acquired());
   6236 
   6237  // If persistent origin is initialized and the directory lock for the
   6238  // initialize persistent origin operation is acquired, we can immediately
   6239  // resolve the promise and return from the function because there can't be a
   6240  // clear storage, shutdown storage, clear origin, or shutdown origin
   6241  // operation which would uninitialize storage (which also includes
   6242  // uninitialization of origins), or which would uninitialize origins
   6243  // directly.
   6244  if (IsPersistentOriginInitialized(aOriginMetadata)) {
   6245    DropDirectoryLock(aDirectoryLock);
   6246 
   6247    return BoolPromise::CreateAndResolve(true, __func__);
   6248  }
   6249 
   6250  auto initializePersistentOriginOp = CreateInitializePersistentOriginOp(
   6251      WrapMovingNotNullUnchecked(this), aOriginMetadata,
   6252      std::move(aDirectoryLock));
   6253 
   6254  RegisterNormalOriginOp(*initializePersistentOriginOp);
   6255 
   6256  initializePersistentOriginOp->RunImmediately();
   6257 
   6258  return Map<BoolPromise>(
   6259      initializePersistentOriginOp->OnResults(),
   6260      [self = RefPtr(this), origin = aOriginMetadata.mOrigin](
   6261          const BoolPromise::ResolveOrRejectValue& aValue) {
   6262        self->NoteInitializedOrigin(PERSISTENCE_TYPE_PERSISTENT, origin);
   6263 
   6264        return aValue.ResolveValue();
   6265      });
   6266 }
   6267 
   6268 RefPtr<BoolPromise> QuotaManager::PersistentOriginInitialized(
   6269    const OriginMetadata& aOriginMetadata) {
   6270  AssertIsOnOwningThread();
   6271  MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6272 
   6273  auto persistentOriginInitializedOp = CreatePersistentOriginInitializedOp(
   6274      WrapMovingNotNullUnchecked(this), aOriginMetadata);
   6275 
   6276  RegisterNormalOriginOp(*persistentOriginInitializedOp);
   6277 
   6278  persistentOriginInitializedOp->RunImmediately();
   6279 
   6280  return persistentOriginInitializedOp->OnResults();
   6281 }
   6282 
   6283 bool QuotaManager::IsPersistentOriginInitialized(
   6284    const OriginMetadata& aOriginMetadata) {
   6285  AssertIsOnOwningThread();
   6286  MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6287 
   6288  return IsOriginInitialized(aOriginMetadata.mPersistenceType,
   6289                             aOriginMetadata.mOrigin);
   6290 }
   6291 
   6292 bool QuotaManager::IsPersistentOriginInitializedInternal(
   6293    const OriginMetadata& aOriginMetadata) const {
   6294  AssertIsOnIOThread();
   6295  MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6296 
   6297  return mInitializedOriginsInternal.Contains(aOriginMetadata.mOrigin);
   6298 }
   6299 
   6300 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
   6301 QuotaManager::EnsurePersistentOriginIsInitializedInternal(
   6302    const OriginMetadata& aOriginMetadata) {
   6303  AssertIsOnIOThread();
   6304  MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6305  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   6306 
   6307  const auto innerFunc = [&aOriginMetadata,
   6308                          this](const auto& firstInitializationAttempt)
   6309      -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
   6310    const auto extraInfo =
   6311        ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
   6312                           aOriginMetadata.mStorageOrigin};
   6313 
   6314    QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
   6315 
   6316    if (mInitializedOriginsInternal.Contains(aOriginMetadata.mOrigin)) {
   6317      MOZ_ASSERT(firstInitializationAttempt.Recorded());
   6318      return std::pair(std::move(directory), false);
   6319    }
   6320 
   6321    QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
   6322 
   6323    QM_TRY_INSPECT(
   6324        const FullOriginMetadata& fullOriginMetadata,
   6325        ([this, created, &directory,
   6326          &aOriginMetadata]() -> Result<FullOriginMetadata, nsresult> {
   6327          if (created) {
   6328            const int64_t timestamp = PR_Now();
   6329 
   6330            FullOriginMetadata fullOriginMetadata{
   6331                aOriginMetadata,
   6332                OriginStateMetadata{/* aLastAccessTime */ timestamp,
   6333                                    /* aLastMaintenanceDate */
   6334                                    Date::FromTimestamp(timestamp).ToDays(),
   6335                                    /* aAccessed */ false,
   6336                                    /* aPersisted */ true},
   6337                ClientUsageArray(), /* aUsage */ 0, kCurrentQuotaVersion};
   6338 
   6339            // Only creating .metadata-v2 to reduce IO.
   6340            QM_TRY(MOZ_TO_RESULT(
   6341                CreateDirectoryMetadata2(*directory, fullOriginMetadata)));
   6342 
   6343            return fullOriginMetadata;
   6344          }
   6345 
   6346          // Get the metadata. We only use the timestamp.
   6347          QM_TRY_INSPECT(const auto& metadata,
   6348                         LoadFullOriginMetadataWithRestore(directory));
   6349 
   6350          MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now());
   6351 
   6352          return metadata;
   6353        }()));
   6354 
   6355    QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, fullOriginMetadata)));
   6356 
   6357    mInitializedOriginsInternal.AppendElement(aOriginMetadata.mOrigin);
   6358 
   6359    return std::pair(std::move(directory), created);
   6360  };
   6361 
   6362  return ExecuteOriginInitialization(
   6363      aOriginMetadata.mOrigin, OriginInitialization::PersistentOrigin,
   6364      "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns,
   6365      innerFunc);
   6366 }
   6367 
   6368 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryOrigin(
   6369    const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent) {
   6370  AssertIsOnOwningThread();
   6371  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6372 
   6373  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   6374      PersistenceScope::CreateFromValue(aOriginMetadata.mPersistenceType),
   6375      OriginScope::FromOrigin(aOriginMetadata),
   6376      ClientStorageScope::CreateFromNull(),
   6377      /* aExclusive */ false);
   6378 
   6379  auto prepareInfo = directoryLock->Prepare();
   6380 
   6381  // If temporary origin is initialized but there's a clear storage, shutdown
   6382  // storage, clear origin, or shutdown origin operation already scheduled, we
   6383  // can't immediately resolve the promise and return from the function because
   6384  // the clear and shutdown storage operations uninitialize storage (which also
   6385  // includes uninitialization of origins) and because clear and shutdown origin
   6386  // operations uninitialize origins directly.
   6387  if (IsTemporaryOriginInitialized(aOriginMetadata) &&
   6388      !prepareInfo.IsBlockedBy(kUninitOriginsAndBroaderCategories)) {
   6389    return BoolPromise::CreateAndResolve(true, __func__);
   6390  }
   6391 
   6392  return directoryLock->Acquire(std::move(prepareInfo))
   6393      ->Then(GetCurrentSerialEventTarget(), __func__,
   6394             [self = RefPtr(this), aOriginMetadata, aCreateIfNonExistent,
   6395              directoryLock](
   6396                 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   6397               if (aValue.IsReject()) {
   6398                 return BoolPromise::CreateAndReject(aValue.RejectValue(),
   6399                                                     __func__);
   6400               }
   6401 
   6402               return self->InitializeTemporaryOrigin(aOriginMetadata,
   6403                                                      aCreateIfNonExistent,
   6404                                                      std::move(directoryLock));
   6405             });
   6406 }
   6407 
   6408 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryOrigin(
   6409    const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent,
   6410    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   6411  AssertIsOnOwningThread();
   6412  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6413  MOZ_ASSERT(aDirectoryLock);
   6414  MOZ_ASSERT(aDirectoryLock->Acquired());
   6415 
   6416  // If temporary origin is initialized and the directory lock for the
   6417  // initialize temporary origin operation is acquired, we can immediately
   6418  // resolve the promise and return from the function because there can't be a
   6419  // clear storage, shutdown storage, clear origin, or shutdown origin
   6420  // operation which would uninitialize storage (which also includes
   6421  // uninitialization of origins), or which would uninitialize origins
   6422  // directly.
   6423  if (IsTemporaryOriginInitialized(aOriginMetadata)) {
   6424    DropDirectoryLock(aDirectoryLock);
   6425 
   6426    return BoolPromise::CreateAndResolve(true, __func__);
   6427  }
   6428 
   6429  auto initializeTemporaryOriginOp = CreateInitializeTemporaryOriginOp(
   6430      WrapMovingNotNullUnchecked(this), aOriginMetadata, aCreateIfNonExistent,
   6431      std::move(aDirectoryLock));
   6432 
   6433  RegisterNormalOriginOp(*initializeTemporaryOriginOp);
   6434 
   6435  initializeTemporaryOriginOp->RunImmediately();
   6436 
   6437  return Map<BoolPromise>(
   6438      initializeTemporaryOriginOp->OnResults(),
   6439      [self = RefPtr(this), persistenceType = aOriginMetadata.mPersistenceType,
   6440       origin = aOriginMetadata.mOrigin](
   6441          const BoolPromise::ResolveOrRejectValue& aValue) {
   6442        self->NoteInitializedOrigin(persistenceType, origin);
   6443 
   6444        return aValue.ResolveValue();
   6445      });
   6446 }
   6447 
   6448 RefPtr<BoolPromise> QuotaManager::TemporaryOriginInitialized(
   6449    const OriginMetadata& aOriginMetadata) {
   6450  AssertIsOnOwningThread();
   6451  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6452 
   6453  auto temporaryOriginInitializedOp = CreateTemporaryOriginInitializedOp(
   6454      WrapMovingNotNullUnchecked(this), aOriginMetadata);
   6455 
   6456  RegisterNormalOriginOp(*temporaryOriginInitializedOp);
   6457 
   6458  temporaryOriginInitializedOp->RunImmediately();
   6459 
   6460  return temporaryOriginInitializedOp->OnResults();
   6461 }
   6462 
   6463 bool QuotaManager::IsTemporaryOriginInitialized(
   6464    const OriginMetadata& aOriginMetadata) {
   6465  AssertIsOnOwningThread();
   6466  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6467 
   6468  return IsOriginInitialized(aOriginMetadata.mPersistenceType,
   6469                             aOriginMetadata.mOrigin);
   6470 }
   6471 
   6472 bool QuotaManager::IsTemporaryOriginInitializedInternal(
   6473    const OriginMetadata& aOriginMetadata) const {
   6474  AssertIsOnIOThread();
   6475  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6476 
   6477  MutexAutoLock lock(mQuotaMutex);
   6478 
   6479  RefPtr<OriginInfo> originInfo =
   6480      LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
   6481 
   6482  return static_cast<bool>(originInfo);
   6483 }
   6484 
   6485 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
   6486 QuotaManager::EnsureTemporaryOriginIsInitializedInternal(
   6487    const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent) {
   6488  AssertIsOnIOThread();
   6489  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6490  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   6491  MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
   6492 
   6493  const auto innerFunc = [&aOriginMetadata, aCreateIfNonExistent,
   6494                          this](const auto&)
   6495      -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
   6496    // Get directory for this origin and persistence type.
   6497    QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
   6498 
   6499    if (IsTemporaryOriginInitializedInternal(aOriginMetadata)) {
   6500      return std::pair(std::move(directory), false);
   6501    }
   6502 
   6503    // We apply the offset only here when setting the initial access time for
   6504    // a new origin. In theory PersistOp::DoDirectoryWork could also honor this
   6505    // pref, but since the pref is intended for testing only, we do it only
   6506    // here for now for simplicity.
   6507    const int64_t timestamp = []() {
   6508      const int64_t now = PR_Now();
   6509      const uint32_t offsetSec = StaticPrefs::
   6510          dom_quotaManager_temporaryStorage_initialOriginAccessTimeOffsetSec();
   6511 
   6512      if (offsetSec > 0) {
   6513        CheckedInt<int64_t> ts(now);
   6514 
   6515        ts -= CheckedInt<int64_t>(offsetSec) * PR_USEC_PER_SEC;
   6516        if (!ts.isValid()) {
   6517          // Offset too large (underflow), use current time as if there was no
   6518          // offset.
   6519 
   6520          QM_WARNING("Initial origin access time offset too large!");
   6521 
   6522          return now;
   6523        }
   6524 
   6525        return ts.value();
   6526      }
   6527 
   6528      return now;
   6529    }();
   6530 
   6531    FullOriginMetadata fullOriginMetadata{
   6532        aOriginMetadata,
   6533        OriginStateMetadata{
   6534            /* aLastAccessTime */ timestamp,
   6535            /* aLastMaintenanceDate */ Date::FromTimestamp(timestamp).ToDays(),
   6536            /* aAccessed */ false,
   6537            /* aPersisted */ false},
   6538        ClientUsageArray(), /* aUsage */ 0, kCurrentQuotaVersion};
   6539 
   6540    if (!aCreateIfNonExistent) {
   6541      InitQuotaForOrigin(fullOriginMetadata, /* aDirectoryExists */ false);
   6542 
   6543      return std::pair(std::move(directory), false);
   6544    }
   6545 
   6546    QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
   6547 
   6548    if (created) {
   6549      // Usually, infallible operations are placed after fallible ones.
   6550      // However, since we lack atomic support for creating the origin
   6551      // directory along with its metadata, we need to add the origin to cached
   6552      // origins right after directory creation.
   6553      AddTemporaryOrigin(fullOriginMetadata);
   6554 
   6555      // Only creating .metadata-v2 to reduce IO.
   6556      QM_TRY(MOZ_TO_RESULT(
   6557          CreateDirectoryMetadata2(*directory, fullOriginMetadata)));
   6558 
   6559      // Don't need to traverse the directory, since it's empty.
   6560      InitQuotaForOrigin(fullOriginMetadata);
   6561    } else {
   6562      QM_TRY_INSPECT(const auto& metadata,
   6563                     LoadFullOriginMetadataWithRestore(directory));
   6564 
   6565      QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, metadata)));
   6566    }
   6567 
   6568    // TODO: If the metadata file exists and we didn't call
   6569    //       LoadFullOriginMetadataWithRestore for it (because the quota info
   6570    //       was loaded from the cache), then the group in the metadata file
   6571    //       may be wrong, so it should be checked and eventually updated.
   6572    //       It's not a big deal that we are not doing it here, because the
   6573    //       origin will be marked as "accessed", so
   6574    //       LoadFullOriginMetadataWithRestore will be called for the metadata
   6575    //       file in next session in LoadQuotaFromCache.
   6576    //       (If a previous origin initialization failed, we actually do call
   6577    //       LoadFullOriginMetadataWithRestore which updates the group)
   6578 
   6579    return std::pair(std::move(directory), created);
   6580  };
   6581 
   6582  return ExecuteOriginInitialization(
   6583      aOriginMetadata.mOrigin, OriginInitialization::TemporaryOrigin,
   6584      "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns,
   6585      innerFunc);
   6586 }
   6587 
   6588 RefPtr<BoolPromise> QuotaManager::InitializePersistentClient(
   6589    const ClientMetadata& aClientMetadata) {
   6590  AssertIsOnOwningThread();
   6591  MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6592 
   6593  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   6594      PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT),
   6595      OriginScope::FromOrigin(aClientMetadata),
   6596      ClientStorageScope::CreateFromClient(aClientMetadata.mClientType),
   6597      /* aExclusive */ false);
   6598 
   6599  auto prepareInfo = directoryLock->Prepare();
   6600 
   6601  // If the persistent client is initialized but there's a clear storage,
   6602  // shutdown storage, clear origin, shutdown origin, clear client, or shutdown
   6603  // client operation already scheduled, we can't immediately resolve the
   6604  // promise and return from the function. This is because:
   6605  // - clear and shutdown storage operations uninitialize storage,
   6606  //   which cascades to origins and clients;
   6607  // - clear and shutdown origin operations uninitialize origins,
   6608  //   which also uninitialize clients;
   6609  // - clear and shutdown client operations uninitialize the client directly.
   6610  if (IsPersistentClientInitialized(aClientMetadata) &&
   6611      !prepareInfo.IsBlockedBy(kUninitClientsAndBroaderCategories)) {
   6612    return BoolPromise::CreateAndResolve(true, __func__);
   6613  }
   6614 
   6615  return directoryLock->Acquire()->Then(
   6616      GetCurrentSerialEventTarget(), __func__,
   6617      [self = RefPtr(this), aClientMetadata,
   6618       directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   6619        if (aValue.IsReject()) {
   6620          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   6621        }
   6622 
   6623        return self->InitializePersistentClient(aClientMetadata,
   6624                                                std::move(directoryLock));
   6625      });
   6626 }
   6627 
   6628 RefPtr<BoolPromise> QuotaManager::InitializePersistentClient(
   6629    const ClientMetadata& aClientMetadata,
   6630    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   6631  AssertIsOnOwningThread();
   6632  MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6633  MOZ_ASSERT(aDirectoryLock);
   6634  MOZ_ASSERT(aDirectoryLock->Acquired());
   6635 
   6636  // If the persistent client is initialized and the directory lock for the
   6637  // initialize persistent client operation is acquired, we can immediately
   6638  // resolve the promise and return from the function because there can't be a
   6639  // clear storage, shutdown storage, clear origin, shutdown origin, clear
   6640  // client, or shutdown client operation in progress. These operations would
   6641  // otherwise uninitialize storage (which cascades to origins and clients),
   6642  // uninitialize origins (which also uninitialize clients), or uninitialize
   6643  // the client directly.
   6644  if (IsPersistentClientInitialized(aClientMetadata)) {
   6645    DropDirectoryLock(aDirectoryLock);
   6646 
   6647    return BoolPromise::CreateAndResolve(true, __func__);
   6648  }
   6649 
   6650  auto initializePersistentClientOp = CreateInitializePersistentClientOp(
   6651      WrapMovingNotNullUnchecked(this), aClientMetadata,
   6652      std::move(aDirectoryLock));
   6653 
   6654  RegisterNormalOriginOp(*initializePersistentClientOp);
   6655 
   6656  initializePersistentClientOp->RunImmediately();
   6657 
   6658  return Map<BoolPromise>(
   6659      initializePersistentClientOp->OnResults(),
   6660      [self = RefPtr(this), origin = aClientMetadata.mOrigin,
   6661       clientType = aClientMetadata.mClientType](
   6662          const BoolPromise::ResolveOrRejectValue& aValue) {
   6663        self->NoteInitializedClient(PERSISTENCE_TYPE_PERSISTENT, origin,
   6664                                    clientType);
   6665 
   6666        return aValue.ResolveValue();
   6667      });
   6668 }
   6669 
   6670 bool QuotaManager::IsPersistentClientInitialized(
   6671    const ClientMetadata& aClientMetadata) {
   6672  AssertIsOnOwningThread();
   6673  MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6674 
   6675  return IsClientInitialized(aClientMetadata.mPersistenceType,
   6676                             aClientMetadata.mOrigin,
   6677                             aClientMetadata.mClientType);
   6678 }
   6679 
   6680 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
   6681 QuotaManager::EnsurePersistentClientIsInitialized(
   6682    const ClientMetadata& aClientMetadata) {
   6683  AssertIsOnIOThread();
   6684  MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
   6685  MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
   6686  MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
   6687  MOZ_DIAGNOSTIC_ASSERT(
   6688      IsPersistentOriginInitializedInternal(aClientMetadata.mOrigin));
   6689 
   6690  QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
   6691 
   6692  QM_TRY(MOZ_TO_RESULT(
   6693      directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
   6694 
   6695  QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
   6696 
   6697  return std::pair(std::move(directory), created);
   6698 }
   6699 
   6700 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient(
   6701    const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent) {
   6702  AssertIsOnOwningThread();
   6703  MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6704 
   6705  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   6706      PersistenceScope::CreateFromValue(aClientMetadata.mPersistenceType),
   6707      OriginScope::FromOrigin(aClientMetadata),
   6708      ClientStorageScope::CreateFromClient(aClientMetadata.mClientType),
   6709      /* aExclusive */ false);
   6710 
   6711  auto prepareInfo = directoryLock->Prepare();
   6712 
   6713  // If the temporary client is initialized but there's a clear storage,
   6714  // shutdown storage, clear origin, shutdown origin, clear client, or shutdown
   6715  // client operation already scheduled, we can't immediately resolve the
   6716  // promise and return from the function. This is because:
   6717  // - clear and shutdown storage operations uninitialize storage,
   6718  //   which cascades to origins and clients;
   6719  // - clear and shutdown origin operations uninitialize origins,
   6720  //   which also uninitialize clients;
   6721  // - clear and shutdown client operations uninitialize the client directly.
   6722  if (IsTemporaryClientInitialized(aClientMetadata) &&
   6723      !prepareInfo.IsBlockedBy(kUninitClientsAndBroaderCategories)) {
   6724    return BoolPromise::CreateAndResolve(true, __func__);
   6725  }
   6726 
   6727  return directoryLock->Acquire()->Then(
   6728      GetCurrentSerialEventTarget(), __func__,
   6729      [self = RefPtr(this), aClientMetadata, aCreateIfNonExistent,
   6730       directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   6731        if (aValue.IsReject()) {
   6732          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   6733        }
   6734 
   6735        return self->InitializeTemporaryClient(
   6736            aClientMetadata, aCreateIfNonExistent, std::move(directoryLock));
   6737      });
   6738 }
   6739 
   6740 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient(
   6741    const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent,
   6742    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   6743  AssertIsOnOwningThread();
   6744  MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6745  MOZ_ASSERT(aDirectoryLock);
   6746  MOZ_ASSERT(aDirectoryLock->Acquired());
   6747 
   6748  // If the temporary client is initialized and the directory lock for the
   6749  // initialize persistent client operation is acquired, we can immediately
   6750  // resolve the promise and return from the function because there can't be a
   6751  // clear storage, shutdown storage, clear origin, shutdown origin, clear
   6752  // client, or shutdown client operation in progress. These operations would
   6753  // otherwise uninitialize storage (which cascades to origins and clients),
   6754  // uninitialize origins (which also uninitialize clients), or uninitialize
   6755  // the client directly.
   6756  if (IsTemporaryClientInitialized(aClientMetadata)) {
   6757    DropDirectoryLock(aDirectoryLock);
   6758 
   6759    return BoolPromise::CreateAndResolve(true, __func__);
   6760  }
   6761 
   6762  auto initializeTemporaryClientOp = CreateInitializeTemporaryClientOp(
   6763      WrapMovingNotNullUnchecked(this), aClientMetadata, aCreateIfNonExistent,
   6764      std::move(aDirectoryLock));
   6765 
   6766  RegisterNormalOriginOp(*initializeTemporaryClientOp);
   6767 
   6768  initializeTemporaryClientOp->RunImmediately();
   6769 
   6770  return Map<BoolPromise>(
   6771      initializeTemporaryClientOp->OnResults(),
   6772      [self = RefPtr(this), persistenceType = aClientMetadata.mPersistenceType,
   6773       origin = aClientMetadata.mOrigin,
   6774       clientType = aClientMetadata.mClientType](
   6775          const BoolPromise::ResolveOrRejectValue& aValue) {
   6776        self->NoteInitializedClient(persistenceType, origin, clientType);
   6777 
   6778        return aValue.ResolveValue();
   6779      });
   6780 }
   6781 
   6782 bool QuotaManager::IsTemporaryClientInitialized(
   6783    const ClientMetadata& aClientMetadata) {
   6784  AssertIsOnOwningThread();
   6785  MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6786 
   6787  return IsClientInitialized(aClientMetadata.mPersistenceType,
   6788                             aClientMetadata.mOrigin,
   6789                             aClientMetadata.mClientType);
   6790 }
   6791 
   6792 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
   6793 QuotaManager::EnsureTemporaryClientIsInitialized(
   6794    const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent) {
   6795  AssertIsOnIOThread();
   6796  MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   6797  MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
   6798  MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
   6799  MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
   6800  MOZ_DIAGNOSTIC_ASSERT(IsTemporaryOriginInitializedInternal(aClientMetadata));
   6801 
   6802  if (!aCreateIfNonExistent) {
   6803    QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
   6804 
   6805    return std::pair(std::move(directory), false);
   6806  }
   6807 
   6808  QM_TRY_UNWRAP(auto directory,
   6809                GetOrCreateTemporaryOriginDirectory(aClientMetadata));
   6810 
   6811  QM_TRY(MOZ_TO_RESULT(
   6812      directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
   6813 
   6814  QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
   6815 
   6816  return std::pair(std::move(directory), created);
   6817 }
   6818 
   6819 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage() {
   6820  AssertIsOnOwningThread();
   6821 
   6822  RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
   6823      PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
   6824                                      PERSISTENCE_TYPE_DEFAULT),
   6825      OriginScope::FromNull(), ClientStorageScope::CreateFromNull(),
   6826      /* aExclusive */ false);
   6827 
   6828  auto prepareInfo = directoryLock->Prepare();
   6829 
   6830  // If temporary storage is initialized but there's a clear storage or
   6831  // shutdown storage operation already scheduled, we can't immediately resolve
   6832  // the promise and return from the function because the clear and shutdown
   6833  // storage operation uninitializes storage.
   6834  if (mTemporaryStorageInitialized &&
   6835      !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) {
   6836    return BoolPromise::CreateAndResolve(true, __func__);
   6837  }
   6838 
   6839  return directoryLock->Acquire(std::move(prepareInfo))
   6840      ->Then(
   6841          GetCurrentSerialEventTarget(), __func__,
   6842          [self = RefPtr(this), directoryLock](
   6843              const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   6844            if (aValue.IsReject()) {
   6845              return BoolPromise::CreateAndReject(aValue.RejectValue(),
   6846                                                  __func__);
   6847            }
   6848 
   6849            return self->InitializeTemporaryStorage(std::move(directoryLock));
   6850          });
   6851 }
   6852 
   6853 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage(
   6854    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   6855  AssertIsOnOwningThread();
   6856  MOZ_ASSERT(aDirectoryLock);
   6857  MOZ_ASSERT(aDirectoryLock->Acquired());
   6858 
   6859  // If temporary storage is initialized and the directory lock for the
   6860  // initialize temporary storage operation is acquired, we can immediately
   6861  // resolve the promise and return from the function because there can't be a
   6862  // clear storage or shutdown storage operation which would uninitialize
   6863  // temporary storage.
   6864  if (mTemporaryStorageInitialized) {
   6865    DropDirectoryLock(aDirectoryLock);
   6866 
   6867    return BoolPromise::CreateAndResolve(true, __func__);
   6868  }
   6869 
   6870  auto initializeTemporaryStorageOp = CreateInitTemporaryStorageOp(
   6871      WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
   6872 
   6873  RegisterNormalOriginOp(*initializeTemporaryStorageOp);
   6874 
   6875  initializeTemporaryStorageOp->RunImmediately();
   6876 
   6877  return initializeTemporaryStorageOp->OnResults()->Then(
   6878      GetCurrentSerialEventTarget(), __func__,
   6879      [self = RefPtr(this)](
   6880          MaybePrincipalMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
   6881        if (aValue.IsReject()) {
   6882          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   6883        }
   6884 
   6885        self->mTemporaryStorageInitialized = true;
   6886 
   6887        if (aValue.ResolveValue() &&
   6888            QuotaPrefs::LazyOriginInitializationEnabled()) {
   6889          self->mBackgroundThreadAccessible.Access()->mUninitializedGroups =
   6890              aValue.ResolveValue().extract();
   6891 
   6892          if (QuotaPrefs::TriggerOriginInitializationInBackgroundEnabled()) {
   6893            self->InitializeAllTemporaryOrigins();
   6894          }
   6895        }
   6896 
   6897        return BoolPromise::CreateAndResolve(true, __func__);
   6898      });
   6899 }
   6900 
   6901 nsresult QuotaManager::InitializeTemporaryStorageInternal() {
   6902  AssertIsOnIOThread();
   6903  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   6904  MOZ_DIAGNOSTIC_ASSERT(!mTemporaryStorageInitializedInternal);
   6905 
   6906  nsCOMPtr<nsIFile> storageDir;
   6907  QM_TRY(MOZ_TO_RESULT(
   6908      NS_NewLocalFile(GetStoragePath(), getter_AddRefs(storageDir))));
   6909 
   6910  // The storage directory must exist before calling GetTemporaryStorageLimit.
   6911  QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir));
   6912 
   6913  (void)created;
   6914 
   6915  QM_TRY_UNWRAP(mTemporaryStorageLimit, GetTemporaryStorageLimit(*storageDir));
   6916 
   6917  QM_TRY(MOZ_TO_RESULT(LoadQuota()));
   6918 
   6919  mTemporaryStorageInitializedInternal = true;
   6920 
   6921  // If origin initialization is done lazily, then there's either no quota
   6922  // information at this point (if the cache couldn't be used) or only
   6923  // partial quota information (origins accessed in a previous session
   6924  // require full initialization). Given that, the cleanup can't be done
   6925  // at this point yet.
   6926  // For the same reason, recording metrics like origins with zero usage can't
   6927  // be done either.
   6928  if (!QuotaPrefs::LazyOriginInitializationEnabled()) {
   6929    CleanupTemporaryStorage();
   6930 
   6931    RecordTemporaryStorageMetrics();
   6932  }
   6933 
   6934  if (mCacheUsable) {
   6935    QM_TRY(InvalidateCache(*mStorageConnection));
   6936  }
   6937 
   6938  return NS_OK;
   6939 }
   6940 
   6941 nsresult QuotaManager::EnsureTemporaryStorageIsInitializedInternal() {
   6942  AssertIsOnIOThread();
   6943  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   6944 
   6945  const auto innerFunc =
   6946      [&](const auto& firstInitializationAttempt) -> nsresult {
   6947    if (mTemporaryStorageInitializedInternal) {
   6948      MOZ_ASSERT(firstInitializationAttempt.Recorded());
   6949      return NS_OK;
   6950    }
   6951 
   6952    // Glean SDK recommends using its own timing APIs where possible. In this
   6953    // case, we use NowExcludingSuspendMs() directly to manually calculate a
   6954    // duration that excludes suspend time. This is a valid exception because
   6955    // our use case is sensitive to suspend, and we need full control over the
   6956    // timing logic.
   6957 
   6958    const auto startExcludingSuspendMs = NowExcludingSuspendMs();
   6959 
   6960    QM_TRY(MOZ_TO_RESULT(InitializeTemporaryStorageInternal()));
   6961 
   6962    const auto endExcludingSuspendMs = NowExcludingSuspendMs();
   6963 
   6964    if (startExcludingSuspendMs && endExcludingSuspendMs) {
   6965      const auto duration = TimeDuration::FromMilliseconds(
   6966          *endExcludingSuspendMs - *startExcludingSuspendMs);
   6967 
   6968      glean::quotamanager_initialize_temporarystorage::
   6969          total_time_excluding_suspend.AccumulateRawDuration(duration);
   6970    }
   6971 
   6972    return NS_OK;
   6973  };
   6974 
   6975  return ExecuteInitialization(
   6976      Initialization::TemporaryStorage,
   6977      "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc);
   6978 }
   6979 
   6980 RefPtr<BoolPromise> QuotaManager::InitializeAllTemporaryOrigins() {
   6981  AssertIsOnOwningThread();
   6982 
   6983  if (mAllTemporaryOriginsInitialized) {
   6984    return BoolPromise::CreateAndResolve(true, __func__);
   6985  }
   6986 
   6987  // Ensure the promise holder is initialized (a promise is created). This is
   6988  // needed because processNextGroup can synchronously attempt to resolve the
   6989  // promise. Without this, consumers may endlessly wait for an unresolved
   6990  // promise, and in debug builds, an assertion failure may occur at shutdown,
   6991  // indicating the promise exists but was never resolved or rejected.
   6992  RefPtr<BoolPromise> promise =
   6993      mInitializeAllTemporaryOriginsPromiseHolder.Ensure(__func__);
   6994 
   6995  if (!mInitializingAllTemporaryOrigins) {
   6996    // TODO: make a copy of mUninitializedGroups and use it as the queue.
   6997 
   6998    mInitializingAllTemporaryOrigins = true;
   6999 
   7000    auto processNextGroup = [self = RefPtr(this)](
   7001                                auto&& processNextGroupCallback) {
   7002      auto backgroundThreadData = self->mBackgroundThreadAccessible.Access();
   7003 
   7004      if (backgroundThreadData->mUninitializedGroups.IsEmpty()) {
   7005        self->mInitializingAllTemporaryOrigins = false;
   7006        self->mAllTemporaryOriginsInitialized = true;
   7007 
   7008        self->mInitializeAllTemporaryOriginsPromiseHolder.ResolveIfExists(
   7009            true, __func__);
   7010 
   7011        return;
   7012      }
   7013 
   7014      if (NS_WARN_IF(IsShuttingDown())) {
   7015        self->mInitializingAllTemporaryOrigins = false;
   7016 
   7017        self->mInitializeAllTemporaryOriginsPromiseHolder.RejectIfExists(
   7018            NS_ERROR_ABORT, __func__);
   7019 
   7020        return;
   7021      }
   7022 
   7023      auto principalMetadata =
   7024          backgroundThreadData->mUninitializedGroups.PopLastElement();
   7025 
   7026      self->InitializeTemporaryGroup(principalMetadata)
   7027          ->Then(GetCurrentSerialEventTarget(), __func__,
   7028                 [processNextGroupCallback](
   7029                     const BoolPromise::ResolveOrRejectValue& aValue) {
   7030                   // TODO: Remove the group from mUninitializedGroups only
   7031                   // when the group initialization succeeded.
   7032 
   7033                   processNextGroupCallback(processNextGroupCallback);
   7034                 });
   7035    };
   7036 
   7037    processNextGroup(/* processNextGroupCallback */ processNextGroup);
   7038  }
   7039 
   7040  return promise;
   7041 }
   7042 
   7043 RefPtr<BoolPromise> QuotaManager::SaveOriginAccessTime(
   7044    const OriginMetadata& aOriginMetadata) {
   7045  AssertIsOnOwningThread();
   7046  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   7047 
   7048  RefPtr<UniversalDirectoryLock> directoryLock =
   7049      CreateSaveOriginAccessTimeLock(*this, aOriginMetadata);
   7050 
   7051  return directoryLock->Acquire()->Then(
   7052      GetCurrentSerialEventTarget(), __func__,
   7053      [self = RefPtr(this), aOriginMetadata,
   7054       directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
   7055        if (aValue.IsReject()) {
   7056          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   7057        }
   7058 
   7059        return self->SaveOriginAccessTime(aOriginMetadata,
   7060                                          std::move(directoryLock));
   7061      });
   7062 }
   7063 
   7064 RefPtr<BoolPromise> QuotaManager::SaveOriginAccessTime(
   7065    const OriginMetadata& aOriginMetadata,
   7066    RefPtr<UniversalDirectoryLock> aDirectoryLock) {
   7067  AssertIsOnOwningThread();
   7068  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   7069  MOZ_ASSERT(aDirectoryLock);
   7070  MOZ_ASSERT(aDirectoryLock->Acquired());
   7071 
   7072  auto saveOriginAccessTimeOp =
   7073      CreateSaveOriginAccessTimeOp(WrapMovingNotNullUnchecked(this),
   7074                                   aOriginMetadata, std::move(aDirectoryLock));
   7075 
   7076  RegisterNormalOriginOp(*saveOriginAccessTimeOp);
   7077 
   7078  saveOriginAccessTimeOp->RunImmediately();
   7079 
   7080  return Map<BoolPromise>(
   7081      saveOriginAccessTimeOp->OnResults(),
   7082      [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
   7083        if (aValue.ResolveValue()) {
   7084          self->IncreaseSaveOriginAccessTimeCount();
   7085        }
   7086 
   7087        return aValue.ResolveValue();
   7088      });
   7089 }
   7090 
   7091 RefPtr<OriginUsageMetadataArrayPromise> QuotaManager::GetUsage(
   7092    bool aGetAll, RefPtr<BoolPromise> aOnCancelPromise) {
   7093  AssertIsOnOwningThread();
   7094 
   7095  auto getUsageOp = CreateGetUsageOp(WrapMovingNotNullUnchecked(this), aGetAll);
   7096 
   7097  RegisterNormalOriginOp(*getUsageOp);
   7098 
   7099  getUsageOp->RunImmediately();
   7100 
   7101  if (aOnCancelPromise) {
   7102    RefPtr<BoolPromise> onCancelPromise = std::move(aOnCancelPromise);
   7103 
   7104    onCancelPromise->Then(
   7105        GetCurrentSerialEventTarget(), __func__,
   7106        [getUsageOp](const BoolPromise::ResolveOrRejectValue& aValue) {
   7107          if (aValue.IsReject()) {
   7108            return;
   7109          }
   7110 
   7111          if (getUsageOp->Cancel()) {
   7112            NS_WARNING("Canceled more than once?!");
   7113          }
   7114        });
   7115  }
   7116 
   7117  return getUsageOp->OnResults();
   7118 }
   7119 
   7120 RefPtr<UsageInfoPromise> QuotaManager::GetOriginUsage(
   7121    const PrincipalInfo& aPrincipalInfo, RefPtr<BoolPromise> aOnCancelPromise) {
   7122  AssertIsOnOwningThread();
   7123 
   7124  auto getOriginUsageOp =
   7125      CreateGetOriginUsageOp(WrapMovingNotNullUnchecked(this), aPrincipalInfo);
   7126 
   7127  RegisterNormalOriginOp(*getOriginUsageOp);
   7128 
   7129  getOriginUsageOp->RunImmediately();
   7130 
   7131  if (aOnCancelPromise) {
   7132    RefPtr<BoolPromise> onCancelPromise = std::move(aOnCancelPromise);
   7133 
   7134    onCancelPromise->Then(
   7135        GetCurrentSerialEventTarget(), __func__,
   7136        [getOriginUsageOp](const BoolPromise::ResolveOrRejectValue& aValue) {
   7137          if (aValue.IsReject()) {
   7138            return;
   7139          }
   7140 
   7141          if (getOriginUsageOp->Cancel()) {
   7142            NS_WARNING("Canceled more than once?!");
   7143          }
   7144        });
   7145  }
   7146 
   7147  return getOriginUsageOp->OnResults();
   7148 }
   7149 
   7150 RefPtr<UInt64Promise> QuotaManager::GetCachedOriginUsage(
   7151    const PrincipalInfo& aPrincipalInfo) {
   7152  AssertIsOnOwningThread();
   7153 
   7154  auto getCachedOriginUsageOp = CreateGetCachedOriginUsageOp(
   7155      WrapMovingNotNullUnchecked(this), aPrincipalInfo);
   7156 
   7157  RegisterNormalOriginOp(*getCachedOriginUsageOp);
   7158 
   7159  getCachedOriginUsageOp->RunImmediately();
   7160 
   7161  return getCachedOriginUsageOp->OnResults();
   7162 }
   7163 
   7164 RefPtr<CStringArrayPromise> QuotaManager::ListOrigins() {
   7165  AssertIsOnOwningThread();
   7166 
   7167  auto listOriginsOp = CreateListOriginsOp(WrapMovingNotNullUnchecked(this));
   7168 
   7169  RegisterNormalOriginOp(*listOriginsOp);
   7170 
   7171  listOriginsOp->RunImmediately();
   7172 
   7173  return listOriginsOp->OnResults();
   7174 }
   7175 
   7176 RefPtr<CStringArrayPromise> QuotaManager::ListCachedOrigins() {
   7177  AssertIsOnOwningThread();
   7178 
   7179  auto listCachedOriginsOp =
   7180      CreateListCachedOriginsOp(WrapMovingNotNullUnchecked(this));
   7181 
   7182  RegisterNormalOriginOp(*listCachedOriginsOp);
   7183 
   7184  listCachedOriginsOp->RunImmediately();
   7185 
   7186  return listCachedOriginsOp->OnResults();
   7187 }
   7188 
   7189 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOrigin(
   7190    const Maybe<PersistenceType>& aPersistenceType,
   7191    const PrincipalInfo& aPrincipalInfo) {
   7192  AssertIsOnOwningThread();
   7193 
   7194  auto clearOriginOp = CreateClearOriginOp(WrapMovingNotNullUnchecked(this),
   7195                                           aPersistenceType, aPrincipalInfo);
   7196 
   7197  RegisterNormalOriginOp(*clearOriginOp);
   7198 
   7199  clearOriginOp->RunImmediately();
   7200 
   7201  return Map<BoolPromise>(
   7202      clearOriginOp->OnResults(),
   7203      [self = RefPtr(this)](
   7204          OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
   7205        self->NoteUninitializedClients(aValue.ResolveValue());
   7206        self->NoteUninitializedOrigins(aValue.ResolveValue());
   7207 
   7208        return true;
   7209      });
   7210 }
   7211 
   7212 RefPtr<BoolPromise> QuotaManager::ClearStoragesForClient(
   7213    Maybe<PersistenceType> aPersistenceType,
   7214    const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
   7215  AssertIsOnOwningThread();
   7216 
   7217  auto clearClientOp =
   7218      CreateClearClientOp(WrapMovingNotNullUnchecked(this), aPersistenceType,
   7219                          aPrincipalInfo, aClientType);
   7220 
   7221  RegisterNormalOriginOp(*clearClientOp);
   7222 
   7223  clearClientOp->RunImmediately();
   7224 
   7225  return Map<BoolPromise>(
   7226      clearClientOp->OnResults(),
   7227      [self = RefPtr(this)](
   7228          ClientMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
   7229        self->NoteUninitializedClients(aValue.ResolveValue());
   7230        return true;
   7231      });
   7232 }
   7233 
   7234 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginPrefix(
   7235    const Maybe<PersistenceType>& aPersistenceType,
   7236    const PrincipalInfo& aPrincipalInfo) {
   7237  AssertIsOnOwningThread();
   7238 
   7239  auto clearStoragesForOriginPrefixOp = CreateClearStoragesForOriginPrefixOp(
   7240      WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo);
   7241 
   7242  RegisterNormalOriginOp(*clearStoragesForOriginPrefixOp);
   7243 
   7244  clearStoragesForOriginPrefixOp->RunImmediately();
   7245 
   7246  return Map<BoolPromise>(
   7247      clearStoragesForOriginPrefixOp->OnResults(),
   7248      [self = RefPtr(this)](
   7249          OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
   7250        self->NoteUninitializedClients(aValue.ResolveValue());
   7251        self->NoteUninitializedOrigins(aValue.ResolveValue());
   7252 
   7253        return true;
   7254      });
   7255 }
   7256 
   7257 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginAttributesPattern(
   7258    const OriginAttributesPattern& aPattern) {
   7259  AssertIsOnOwningThread();
   7260 
   7261  auto clearDataOp =
   7262      CreateClearDataOp(WrapMovingNotNullUnchecked(this), aPattern);
   7263 
   7264  RegisterNormalOriginOp(*clearDataOp);
   7265 
   7266  clearDataOp->RunImmediately();
   7267 
   7268  return Map<BoolPromise>(
   7269      clearDataOp->OnResults(),
   7270      [self = RefPtr(this)](
   7271          OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
   7272        self->NoteUninitializedClients(aValue.ResolveValue());
   7273        self->NoteUninitializedOrigins(aValue.ResolveValue());
   7274 
   7275        return true;
   7276      });
   7277 }
   7278 
   7279 RefPtr<BoolPromise> QuotaManager::ClearPrivateRepository() {
   7280  AssertIsOnOwningThread();
   7281 
   7282  auto clearPrivateRepositoryOp =
   7283      CreateClearPrivateRepositoryOp(WrapMovingNotNullUnchecked(this));
   7284 
   7285  RegisterNormalOriginOp(*clearPrivateRepositoryOp);
   7286 
   7287  clearPrivateRepositoryOp->RunImmediately();
   7288 
   7289  return Map<BoolPromise>(
   7290      clearPrivateRepositoryOp->OnResults(),
   7291      [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
   7292        self->NoteUninitializedClients(PERSISTENCE_TYPE_PRIVATE);
   7293        self->NoteUninitializedRepository(PERSISTENCE_TYPE_PRIVATE);
   7294 
   7295        return aValue.ResolveValue();
   7296      });
   7297 }
   7298 
   7299 RefPtr<BoolPromise> QuotaManager::ClearStorage() {
   7300  AssertIsOnOwningThread();
   7301 
   7302  auto clearStorageOp = CreateClearStorageOp(WrapMovingNotNullUnchecked(this));
   7303 
   7304  RegisterNormalOriginOp(*clearStorageOp);
   7305 
   7306  clearStorageOp->RunImmediately();
   7307 
   7308  return clearStorageOp->OnResults()->Then(
   7309      GetCurrentSerialEventTarget(), __func__,
   7310      [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
   7311        if (aValue.IsReject()) {
   7312          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   7313        }
   7314 
   7315        self->mInitializedClients.Clear();
   7316        self->mInitializedOrigins.Clear();
   7317        self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Clear();
   7318        self->mAllTemporaryOriginsInitialized = false;
   7319        self->mTemporaryStorageInitialized = false;
   7320        self->mStorageInitialized = false;
   7321 
   7322        return BoolPromise::CreateAndResolve(true, __func__);
   7323      });
   7324 }
   7325 
   7326 RefPtr<BoolPromise> QuotaManager::ShutdownStoragesForOrigin(
   7327    Maybe<PersistenceType> aPersistenceType,
   7328    const PrincipalInfo& aPrincipalInfo) {
   7329  AssertIsOnOwningThread();
   7330 
   7331  auto shutdownOriginOp = CreateShutdownOriginOp(
   7332      WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo);
   7333 
   7334  RegisterNormalOriginOp(*shutdownOriginOp);
   7335 
   7336  shutdownOriginOp->RunImmediately();
   7337 
   7338  return Map<BoolPromise>(
   7339      shutdownOriginOp->OnResults(),
   7340      [self = RefPtr(this)](
   7341          OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
   7342        self->NoteUninitializedClients(aValue.ResolveValue());
   7343        self->NoteUninitializedOrigins(aValue.ResolveValue());
   7344 
   7345        return true;
   7346      });
   7347 }
   7348 
   7349 RefPtr<BoolPromise> QuotaManager::ShutdownStoragesForClient(
   7350    Maybe<PersistenceType> aPersistenceType,
   7351    const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
   7352  AssertIsOnOwningThread();
   7353 
   7354  auto shutdownClientOp =
   7355      CreateShutdownClientOp(WrapMovingNotNullUnchecked(this), aPersistenceType,
   7356                             aPrincipalInfo, aClientType);
   7357 
   7358  RegisterNormalOriginOp(*shutdownClientOp);
   7359 
   7360  shutdownClientOp->RunImmediately();
   7361 
   7362  return Map<BoolPromise>(
   7363      shutdownClientOp->OnResults(),
   7364      [self = RefPtr(this)](
   7365          ClientMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
   7366        self->NoteUninitializedClients(aValue.ResolveValue());
   7367 
   7368        return true;
   7369      });
   7370 }
   7371 
   7372 RefPtr<BoolPromise> QuotaManager::ShutdownStorage(
   7373    Maybe<OriginOperationCallbackOptions> aCallbackOptions,
   7374    Maybe<OriginOperationCallbacks&> aCallbacks) {
   7375  AssertIsOnOwningThread();
   7376 
   7377  auto shutdownStorageOp =
   7378      CreateShutdownStorageOp(WrapMovingNotNullUnchecked(this));
   7379 
   7380  RegisterNormalOriginOp(*shutdownStorageOp);
   7381 
   7382  shutdownStorageOp->RunImmediately();
   7383 
   7384  if (aCallbackOptions.isSome() && aCallbacks.isSome()) {
   7385    aCallbacks.ref() = shutdownStorageOp->GetCallbacks(aCallbackOptions.ref());
   7386  }
   7387 
   7388  return shutdownStorageOp->OnResults()->Then(
   7389      GetCurrentSerialEventTarget(), __func__,
   7390      [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
   7391        if (aValue.IsReject()) {
   7392          return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
   7393        }
   7394 
   7395        self->mInitializedClients.Clear();
   7396        self->mInitializedOrigins.Clear();
   7397        self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Clear();
   7398        self->mAllTemporaryOriginsInitialized = false;
   7399        self->mTemporaryStorageInitialized = false;
   7400        self->mStorageInitialized = false;
   7401 
   7402        return BoolPromise::CreateAndResolve(true, __func__);
   7403      });
   7404 }
   7405 
   7406 void QuotaManager::ShutdownStorageInternal() {
   7407  AssertIsOnIOThread();
   7408 
   7409  if (mStorageConnection) {
   7410    mInitializationInfo.ResetOriginInitializationInfos();
   7411    mInitializedOriginsInternal.Clear();
   7412 
   7413    if (mTemporaryStorageInitializedInternal) {
   7414      if (mCacheUsable) {
   7415        UnloadQuota();
   7416      } else {
   7417        RemoveQuota();
   7418      }
   7419 
   7420      RemoveTemporaryOrigins();
   7421 
   7422      mTemporaryStorageInitializedInternal = false;
   7423    }
   7424 
   7425    ReleaseIOThreadObjects();
   7426 
   7427    mStorageConnection = nullptr;
   7428    mCacheUsable = false;
   7429  }
   7430 
   7431  mInitializationInfo.ResetFirstInitializationAttempts();
   7432 }
   7433 
   7434 Result<bool, nsresult> QuotaManager::EnsureOriginDirectory(
   7435    nsIFile& aDirectory) {
   7436  AssertIsOnIOThread();
   7437 
   7438  QM_TRY_INSPECT(const bool& exists,
   7439                 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
   7440 
   7441  if (!exists) {
   7442    QM_TRY_INSPECT(
   7443        const auto& leafName,
   7444        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aDirectory, GetLeafName)
   7445            .map([](const auto& leafName) {
   7446              return NS_ConvertUTF16toUTF8(leafName);
   7447            }));
   7448 
   7449    QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE),
   7450           [](const auto&) {
   7451             QM_WARNING(
   7452                 "Preventing creation of a new origin directory which is not "
   7453                 "supported by our origin parser or is obsolete!");
   7454           });
   7455  }
   7456 
   7457  QM_TRY_RETURN(EnsureDirectory(aDirectory));
   7458 }
   7459 
   7460 nsresult QuotaManager::AboutToClearOrigins(
   7461    const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope,
   7462    const ClientStorageScope& aClientStorageScope) {
   7463  AssertIsOnIOThread();
   7464  MOZ_ASSERT(aClientStorageScope.IsClient() || aClientStorageScope.IsNull());
   7465 
   7466  if (aClientStorageScope.IsNull()) {
   7467    for (Client::Type type : AllClientTypes()) {
   7468      QM_TRY(MOZ_TO_RESULT((*mClients)[type]->AboutToClearOrigins(
   7469          aPersistenceScope, aOriginScope)));
   7470    }
   7471  } else {
   7472    QM_TRY(MOZ_TO_RESULT(
   7473        (*mClients)[aClientStorageScope.GetClientType()]->AboutToClearOrigins(
   7474            aPersistenceScope, aOriginScope)));
   7475  }
   7476 
   7477  return NS_OK;
   7478 }
   7479 
   7480 void QuotaManager::OriginClearCompleted(
   7481    const OriginMetadata& aOriginMetadata,
   7482    const ClientStorageScope& aClientStorageScope) {
   7483  AssertIsOnIOThread();
   7484  MOZ_ASSERT(aClientStorageScope.IsClient() || aClientStorageScope.IsNull());
   7485 
   7486  if (aClientStorageScope.IsNull()) {
   7487    if (aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
   7488      mInitializedOriginsInternal.RemoveElement(aOriginMetadata.mOrigin);
   7489    } else {
   7490      RemoveTemporaryOrigin(aOriginMetadata);
   7491    }
   7492 
   7493    for (Client::Type type : AllClientTypes()) {
   7494      (*mClients)[type]->OnOriginClearCompleted(aOriginMetadata);
   7495    }
   7496  } else {
   7497    (*mClients)[aClientStorageScope.GetClientType()]->OnOriginClearCompleted(
   7498        aOriginMetadata);
   7499  }
   7500 }
   7501 
   7502 void QuotaManager::RepositoryClearCompleted(PersistenceType aPersistenceType) {
   7503  AssertIsOnIOThread();
   7504 
   7505  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
   7506    mInitializedOriginsInternal.Clear();
   7507  } else {
   7508    RemoveTemporaryOrigins(aPersistenceType);
   7509  }
   7510 
   7511  for (Client::Type type : AllClientTypes()) {
   7512    (*mClients)[type]->OnRepositoryClearCompleted(aPersistenceType);
   7513  }
   7514 }
   7515 
   7516 Client* QuotaManager::GetClient(Client::Type aClientType) {
   7517  MOZ_ASSERT(aClientType >= Client::IDB);
   7518  MOZ_ASSERT(aClientType < Client::TypeMax());
   7519 
   7520  return (*mClients)[aClientType];
   7521 }
   7522 
   7523 const AutoTArray<Client::Type, Client::TYPE_MAX>&
   7524 QuotaManager::AllClientTypes() {
   7525  if (CachedNextGenLocalStorageEnabled()) {
   7526    return *mAllClientTypes;
   7527  }
   7528  return *mAllClientTypesExceptLS;
   7529 }
   7530 
   7531 bool QuotaManager::IsThumbnailPrivateIdentityIdKnown() const {
   7532  AssertIsOnIOThread();
   7533 
   7534  return mIOThreadAccessible.Access()->mThumbnailPrivateIdentityId.isSome();
   7535 }
   7536 
   7537 uint32_t QuotaManager::GetThumbnailPrivateIdentityId() const {
   7538  AssertIsOnIOThread();
   7539 
   7540  return mIOThreadAccessible.Access()->mThumbnailPrivateIdentityId.ref();
   7541 }
   7542 
   7543 void QuotaManager::SetThumbnailPrivateIdentityId(
   7544    uint32_t aThumbnailPrivateIdentityId) {
   7545  AssertIsOnIOThread();
   7546 
   7547  auto ioThreadData = mIOThreadAccessible.Access();
   7548 
   7549  ioThreadData->mThumbnailPrivateIdentityId = Some(aThumbnailPrivateIdentityId);
   7550  ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount = 0;
   7551 
   7552  for (auto iter = ioThreadData->mAllTemporaryOrigins.Iter(); !iter.Done();
   7553       iter.Next()) {
   7554    auto& array = iter.Data();
   7555 
   7556    for (const auto& originMetadata : array) {
   7557      if (IsUserContextSuffix(originMetadata.mSuffix,
   7558                              GetThumbnailPrivateIdentityId())) {
   7559        AssertNoOverflow(
   7560            ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1);
   7561        ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount++;
   7562      }
   7563    }
   7564  }
   7565 }
   7566 
   7567 /* static */
   7568 uint64_t QuotaManager::GetGroupLimitForLimit(uint64_t aLimit) {
   7569  // To avoid one group evicting all the rest, limit the amount any one group
   7570  // can use to 20% resp. a fifth. To prevent individual sites from using
   7571  // exorbitant amounts of storage where there is a lot of free space, cap the
   7572  // group limit to 10GB.
   7573  const auto x = std::min<uint64_t>(aLimit / 5, 10 GB);
   7574 
   7575  // In low-storage situations, make an exception (while not exceeding the total
   7576  // storage limit).
   7577  return std::min<uint64_t>(aLimit, std::max<uint64_t>(x, 10 MB));
   7578 }
   7579 
   7580 uint64_t QuotaManager::GetGroupLimit() const {
   7581  return GetGroupLimitForLimit(mTemporaryStorageLimit);
   7582 }
   7583 
   7584 Maybe<OriginStateMetadata> QuotaManager::GetOriginStateMetadata(
   7585    const OriginMetadata& aOriginMetadata) {
   7586  AssertIsOnIOThread();
   7587  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   7588  MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
   7589 
   7590  MutexAutoLock lock(mQuotaMutex);
   7591 
   7592  RefPtr<OriginInfo> originInfo =
   7593      LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
   7594  if (originInfo) {
   7595    return Some(originInfo->LockedFlattenToOriginStateMetadata());
   7596  }
   7597 
   7598  return Nothing();
   7599 }
   7600 
   7601 std::pair<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate(
   7602    const OriginMetadata& aOriginMetadata) {
   7603  AssertIsOnIOThread();
   7604 
   7605  uint64_t totalGroupUsage = 0;
   7606 
   7607  {
   7608    MutexAutoLock lock(mQuotaMutex);
   7609 
   7610    GroupInfoPair* pair;
   7611    if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   7612      for (const PersistenceType type : kBestEffortPersistenceTypes) {
   7613        RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
   7614        if (groupInfo) {
   7615          if (type == PERSISTENCE_TYPE_DEFAULT) {
   7616            RefPtr<OriginInfo> originInfo =
   7617                groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
   7618 
   7619            if (originInfo && originInfo->LockedPersisted()) {
   7620              return std::pair(mTemporaryStorageUsage, mTemporaryStorageLimit);
   7621            }
   7622          }
   7623 
   7624          AssertNoOverflow(totalGroupUsage, groupInfo->mUsage);
   7625          totalGroupUsage += groupInfo->mUsage;
   7626        }
   7627      }
   7628    }
   7629  }
   7630 
   7631  return std::pair(totalGroupUsage, GetGroupLimit());
   7632 }
   7633 
   7634 uint64_t QuotaManager::GetOriginUsage(
   7635    const PrincipalMetadata& aPrincipalMetadata) {
   7636  AssertIsOnIOThread();
   7637 
   7638  uint64_t usage = 0;
   7639 
   7640  {
   7641    MutexAutoLock lock(mQuotaMutex);
   7642 
   7643    GroupInfoPair* pair;
   7644    if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) {
   7645      for (const PersistenceType type : kBestEffortPersistenceTypes) {
   7646        RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
   7647        if (groupInfo) {
   7648          RefPtr<OriginInfo> originInfo =
   7649              groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin);
   7650          if (originInfo) {
   7651            AssertNoOverflow(usage, originInfo->LockedUsage());
   7652            usage += originInfo->LockedUsage();
   7653          }
   7654        }
   7655      }
   7656    }
   7657  }
   7658 
   7659  return usage;
   7660 }
   7661 
   7662 Maybe<FullOriginMetadata> QuotaManager::GetFullOriginMetadata(
   7663    const OriginMetadata& aOriginMetadata) {
   7664  AssertIsOnIOThread();
   7665  MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
   7666  MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
   7667 
   7668  MutexAutoLock lock(mQuotaMutex);
   7669 
   7670  RefPtr<OriginInfo> originInfo =
   7671      LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
   7672  if (originInfo) {
   7673    return Some(originInfo->LockedFlattenToFullOriginMetadata());
   7674  }
   7675 
   7676  return Nothing();
   7677 }
   7678 
   7679 uint64_t QuotaManager::TotalDirectoryIterations() const {
   7680  AssertIsOnIOThread();
   7681 
   7682  return mIOThreadAccessible.Access()->mTotalDirectoryIterations;
   7683 }
   7684 
   7685 uint64_t QuotaManager::SaveOriginAccessTimeCount() const {
   7686  AssertIsOnOwningThread();
   7687 
   7688  return mBackgroundThreadAccessible.Access()->mSaveOriginAccessTimeCount;
   7689 }
   7690 
   7691 uint64_t QuotaManager::SaveOriginAccessTimeCountInternal() const {
   7692  AssertIsOnIOThread();
   7693 
   7694  return mIOThreadAccessible.Access()->mSaveOriginAccessTimeCount;
   7695 }
   7696 
   7697 // static
   7698 void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
   7699                                const nsACString& aOrigin,
   7700                                Client::Type aClientType,
   7701                                nsACString& aDatabaseId) {
   7702  nsAutoCString str;
   7703  str.AppendInt(aPersistenceType);
   7704  str.Append('*');
   7705  str.Append(aOrigin);
   7706  str.Append('*');
   7707  str.AppendInt(aClientType);
   7708 
   7709  aDatabaseId = str;
   7710 }
   7711 
   7712 // static
   7713 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
   7714  MOZ_ASSERT(!aOrigin.IsEmpty());
   7715 
   7716  // The first prompt is not required for these origins.
   7717  if (aOrigin.EqualsLiteral(kChromeOrigin) ||
   7718      StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
   7719      StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
   7720      StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
   7721    return true;
   7722  }
   7723 
   7724  return false;
   7725 }
   7726 
   7727 // static
   7728 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1,
   7729                                         const nsACString& aOrigin2) {
   7730  return MakeSanitizedOriginCString(aOrigin1) ==
   7731         MakeSanitizedOriginCString(aOrigin2);
   7732 }
   7733 
   7734 // static
   7735 Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin(
   7736    const nsACString& aOrigin) {
   7737  // An origin string either corresponds to a SystemPrincipalInfo or a
   7738  // ContentPrincipalInfo, see
   7739  // QuotaManager::GetOriginFromValidatedPrincipalInfo.
   7740 
   7741  nsCString spec;
   7742  OriginAttributes attrs;
   7743  nsCString originalSuffix;
   7744  const OriginParser::ResultType result = OriginParser::ParseOrigin(
   7745      MakeSanitizedOriginCString(aOrigin), spec, &attrs, originalSuffix);
   7746  QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
   7747 
   7748  QM_TRY_INSPECT(
   7749      const auto& principal,
   7750      ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
   7751        if (spec.EqualsLiteral(kChromeOrigin)) {
   7752          return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
   7753        }
   7754 
   7755        nsCOMPtr<nsIURI> uri;
   7756        QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
   7757 
   7758        return nsCOMPtr<nsIPrincipal>(
   7759            BasePrincipal::CreateContentPrincipal(uri, attrs));
   7760      }()));
   7761  QM_TRY(MOZ_TO_RESULT(principal));
   7762 
   7763  PrincipalInfo principalInfo;
   7764  QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
   7765 
   7766  return std::move(principalInfo);
   7767 }
   7768 
   7769 // static
   7770 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; }
   7771 
   7772 uint64_t QuotaManager::LockedCollectOriginsForEviction(
   7773    uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks)
   7774    MOZ_REQUIRES(mQuotaMutex) {
   7775  mQuotaMutex.AssertCurrentThreadOwns();
   7776 
   7777  RefPtr<CollectOriginsHelper> helper =
   7778      new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
   7779 
   7780  // Unlock while calling out to XPCOM (code behind the dispatch method needs
   7781  // to acquire its own lock which can potentially lead to a deadlock and it
   7782  // also calls an observer that can do various stuff like IO, so it's better
   7783  // to not hold our mutex while that happens).
   7784  {
   7785    MutexAutoUnlock autoUnlock(mQuotaMutex);
   7786 
   7787    MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
   7788  }
   7789 
   7790  return helper->BlockAndReturnOriginsForEviction(aLocks);
   7791 }
   7792 
   7793 void QuotaManager::LockedRemoveQuotaForRepository(
   7794    PersistenceType aPersistenceType) {
   7795  mQuotaMutex.AssertCurrentThreadOwns();
   7796  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   7797 
   7798  for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
   7799    auto& pair = iter.Data();
   7800 
   7801    if (RefPtr<GroupInfo> groupInfo =
   7802            pair->LockedGetGroupInfo(aPersistenceType)) {
   7803      groupInfo->LockedRemoveOriginInfos();
   7804 
   7805      pair->LockedClearGroupInfo(aPersistenceType);
   7806 
   7807      if (!pair->LockedHasGroupInfos()) {
   7808        iter.Remove();
   7809      }
   7810    }
   7811  }
   7812 }
   7813 
   7814 void QuotaManager::LockedRemoveQuotaForOrigin(
   7815    const OriginMetadata& aOriginMetadata) {
   7816  mQuotaMutex.AssertCurrentThreadOwns();
   7817  MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   7818 
   7819  GroupInfoPair* pair;
   7820  if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   7821    return;
   7822  }
   7823 
   7824  MOZ_ASSERT(pair);
   7825 
   7826  if (RefPtr<GroupInfo> groupInfo =
   7827          pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType)) {
   7828    groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin);
   7829 
   7830    if (!groupInfo->LockedHasOriginInfos()) {
   7831      pair->LockedClearGroupInfo(aOriginMetadata.mPersistenceType);
   7832 
   7833      if (!pair->LockedHasGroupInfos()) {
   7834        mGroupInfoPairs.Remove(aOriginMetadata.mGroup);
   7835      }
   7836    }
   7837  }
   7838 }
   7839 
   7840 bool QuotaManager::LockedHasGroupInfoPair(const nsACString& aGroup) const {
   7841  mQuotaMutex.AssertCurrentThreadOwns();
   7842 
   7843  return mGroupInfoPairs.Get(aGroup, nullptr);
   7844 }
   7845 
   7846 already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
   7847    PersistenceType aPersistenceType, const nsACString& aSuffix,
   7848    const nsACString& aGroup) {
   7849  mQuotaMutex.AssertCurrentThreadOwns();
   7850  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   7851 
   7852  GroupInfoPair* const pair =
   7853      mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup);
   7854 
   7855  RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
   7856  if (!groupInfo) {
   7857    groupInfo = new GroupInfo(pair, aPersistenceType);
   7858    pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
   7859  }
   7860 
   7861  return groupInfo.forget();
   7862 }
   7863 
   7864 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
   7865    PersistenceType aPersistenceType,
   7866    const OriginMetadata& aOriginMetadata) const {
   7867  mQuotaMutex.AssertCurrentThreadOwns();
   7868  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
   7869 
   7870  GroupInfoPair* pair;
   7871  if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
   7872    RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
   7873    if (groupInfo) {
   7874      return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
   7875    }
   7876  }
   7877 
   7878  return nullptr;
   7879 }
   7880 
   7881 template <typename Collect, typename Pred>
   7882 QuotaManager::OriginInfosFlatTraversable
   7883 QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) {
   7884  OriginInfosFlatTraversable originInfos;
   7885 
   7886  std::forward<Collect>(aCollect)(MakeBackInserter(originInfos));
   7887 
   7888  originInfos.Sort(OriginInfoAccessTimeComparator());
   7889 
   7890  const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(),
   7891                                    std::forward<Pred>(aPred));
   7892 
   7893  originInfos.TruncateLength(foundIt - originInfos.cbegin());
   7894 
   7895  return originInfos;
   7896 }
   7897 
   7898 QuotaManager::OriginInfosNestedTraversable
   7899 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
   7900  MutexAutoLock lock(mQuotaMutex);
   7901 
   7902  OriginInfosNestedTraversable originInfos;
   7903 
   7904  for (const auto& entry : mGroupInfoPairs) {
   7905    const auto& pair = entry.GetData();
   7906 
   7907    MOZ_ASSERT(!entry.GetKey().IsEmpty());
   7908    MOZ_ASSERT(pair);
   7909 
   7910    uint64_t groupUsage = 0;
   7911 
   7912    const RefPtr<GroupInfo> temporaryGroupInfo =
   7913        pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
   7914    if (temporaryGroupInfo) {
   7915      groupUsage += temporaryGroupInfo->mUsage;
   7916    }
   7917 
   7918    const RefPtr<GroupInfo> defaultGroupInfo =
   7919        pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
   7920    if (defaultGroupInfo) {
   7921      groupUsage += defaultGroupInfo->mUsage;
   7922    }
   7923 
   7924    const RefPtr<GroupInfo> privateGroupInfo =
   7925        pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
   7926    if (privateGroupInfo) {
   7927      groupUsage += privateGroupInfo->mUsage;
   7928    }
   7929 
   7930    if (groupUsage > 0) {
   7931      QuotaManager* quotaManager = QuotaManager::Get();
   7932      MOZ_ASSERT(quotaManager, "Shouldn't be null!");
   7933 
   7934      if (groupUsage > quotaManager->GetGroupLimit()) {
   7935        originInfos.AppendElement(CollectLRUOriginInfosUntil(
   7936            [&pair](auto inserter) {
   7937              pair->MaybeInsertNonPersistedOriginInfos(std::move(inserter));
   7938            },
   7939            [&groupUsage, quotaManager](const auto& originInfo) {
   7940              groupUsage -= originInfo->LockedUsage();
   7941 
   7942              return groupUsage <= quotaManager->GetGroupLimit();
   7943            }));
   7944      }
   7945    }
   7946  }
   7947 
   7948  return originInfos;
   7949 }
   7950 
   7951 // Uses the same return type as the methods above, so the result can
   7952 // eventually be passed to ClearOrigins to easily clear such origins.
   7953 QuotaManager::OriginInfosNestedTraversable
   7954 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
   7955  MutexAutoLock lock(mQuotaMutex);
   7956 
   7957  QuotaManager::OriginInfosNestedTraversable res;
   7958  res.AppendElement(CollectLRUOriginInfosUntil(
   7959      // XXX The lambda only needs to capture this, but due to Bug 1421435 it
   7960      // can't.
   7961      [&](auto inserter) {
   7962        for (const auto& entry : mGroupInfoPairs) {
   7963          const auto& pair = entry.GetData();
   7964 
   7965          MOZ_ASSERT(!entry.GetKey().IsEmpty());
   7966          MOZ_ASSERT(pair);
   7967 
   7968          pair->MaybeInsertNonPersistedOriginInfos(inserter);
   7969        }
   7970      },
   7971      [temporaryStorageUsage = mTemporaryStorageUsage,
   7972       temporaryStorageLimit = mTemporaryStorageLimit,
   7973       doomedUsage = uint64_t{0}](const auto& originInfo) mutable {
   7974        if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) {
   7975          return true;
   7976        }
   7977 
   7978        doomedUsage += originInfo->LockedUsage();
   7979        return false;
   7980      }));
   7981 
   7982  return res;
   7983 }
   7984 
   7985 QuotaManager::OriginInfosNestedTraversable
   7986 QuotaManager::GetOriginInfosWithZeroUsage(
   7987    const Maybe<int64_t>& aCutoffAccessTime) const {
   7988  MutexAutoLock lock(mQuotaMutex);
   7989 
   7990  QuotaManager::OriginInfosNestedTraversable res;
   7991 
   7992  OriginInfosFlatTraversable originInfos;
   7993 
   7994  auto inserter = MakeBackInserter(originInfos);
   7995 
   7996  for (const auto& entry : mGroupInfoPairs) {
   7997    const auto& pair = entry.GetData();
   7998 
   7999    MOZ_ASSERT(!entry.GetKey().IsEmpty());
   8000    MOZ_ASSERT(pair);
   8001 
   8002    pair->MaybeInsertNonPersistedZeroUsageOriginInfos(inserter,
   8003                                                      aCutoffAccessTime);
   8004  }
   8005 
   8006  res.AppendElement(std::move(originInfos));
   8007 
   8008  return res;
   8009 }
   8010 
   8011 // Based on discussions in
   8012 // https://stackoverflow.com/questions/41847525/should-templated-functions-take-lambda-arguments-by-value-or-by-rvalue-reference
   8013 // and
   8014 // https://stackoverflow.com/questions/56988299/should-i-pass-a-lambda-by-const-reference,
   8015 // passing a callable by universal (forwarding) reference is considered a more
   8016 // flexible choice from an interface point of view.
   8017 //
   8018 // In future, we should also ensure this pattern is compatible with clang-tidy
   8019 // checks such as modernize-pass-by-value and consider enabling
   8020 // cppcoreguidelines-missing-std-forward to catch potential oversights.
   8021 template <typename Checker>
   8022 void QuotaManager::ClearOrigins(
   8023    const OriginInfosNestedTraversable& aDoomedOriginInfos, Checker&& aChecker,
   8024    const Maybe<size_t>& aMaxOriginsToClear) {
   8025  AssertIsOnIOThread();
   8026 
   8027  if (QuotaManager::IsShuttingDown()) {
   8028    // Don't block shutdown.
   8029    return;
   8030  }
   8031 
   8032  auto doomedOriginInfos =
   8033      Flatten<OriginInfosFlatTraversable::value_type>(aDoomedOriginInfos);
   8034 
   8035  // If there's nothing to do, exit early.
   8036  if (doomedOriginInfos.begin() == doomedOriginInfos.end()) {
   8037    return;
   8038  }
   8039 
   8040  // It's safer to take ownership of the checker and keep it outside the loop
   8041  // (the checker can have side effects or use move).
   8042  std::decay_t<Checker> checker = std::forward<Checker>(aChecker);
   8043 
   8044  // If we are in shutdown, we could break off early from clearing origins.
   8045  // In such cases, we would like to track the ones that were already cleared
   8046  // up, such that other essential cleanup could be performed on clearedOrigins.
   8047  // clearedOrigins is used in calls to LockedRemoveQuotaForOrigin and
   8048  // OriginClearCompleted below. We could have used a collection of OriginInfos
   8049  // rather than flattening them to OriginMetadata but groupInfo in OriginInfo
   8050  // is just a raw ptr and LockedRemoveQuotaForOrigin might delete groupInfo and
   8051  // as a result, we would not be able to get origin persistence type required
   8052  // in OriginClearCompleted call after lockedRemoveQuotaForOrigin call.
   8053  nsTArray<OriginMetadata> clearedOrigins;
   8054 
   8055  // XXX Does this need to be done a) in order and/or b) sequentially?
   8056  for (const auto& doomedOriginInfo : doomedOriginInfos) {
   8057    std::invoke(checker, doomedOriginInfo);
   8058 
   8059    //  TODO: We are currently only checking for this flag here which
   8060    //  means that we cannot break off once we start cleaning an origin. It
   8061    //  could be better if we could check for shutdown flag while cleaning an
   8062    //  origin such that we could break off early from the cleaning process if
   8063    //  we are stuck cleaning on one huge origin. Bug1797098 has been filed to
   8064    //  track this.
   8065    if (QuotaManager::IsShuttingDown()) {
   8066      break;
   8067    }
   8068 
   8069    auto originMetadata = doomedOriginInfo->FlattenToOriginMetadata();
   8070 
   8071    DeleteOriginDirectory(originMetadata);
   8072 
   8073    clearedOrigins.AppendElement(std::move(originMetadata));
   8074 
   8075    if (aMaxOriginsToClear && clearedOrigins.Length() >= *aMaxOriginsToClear) {
   8076      break;
   8077    }
   8078  }
   8079 
   8080  {
   8081    MutexAutoLock lock(mQuotaMutex);
   8082 
   8083    for (const auto& clearedOrigin : clearedOrigins) {
   8084      LockedRemoveQuotaForOrigin(clearedOrigin);
   8085    }
   8086  }
   8087 
   8088  for (const auto& clearedOrigin : clearedOrigins) {
   8089    OriginClearCompleted(clearedOrigin, ClientStorageScope::CreateFromNull());
   8090  }
   8091 }
   8092 
   8093 void QuotaManager::CleanupTemporaryStorage() {
   8094  AssertIsOnIOThread();
   8095 
   8096  if (StaticPrefs::
   8097          dom_quotaManager_temporaryStorage_clearNonPersistedZeroUsageOrigins()) {
   8098    // XXX Ideally the clearing would be done asynchronously by storage
   8099    // maintenance service once available.
   8100 
   8101    // We hardcode a 7-day cutoff for "recently used" origins. Even if such
   8102    // origins have zero usage, skipping them avoids the performance penalty
   8103    // of repeatedly recreating origin directories and metadata files. The
   8104    // value is fixed for now to keep things simple, but could be made
   8105    // configurable in the future if needed.
   8106    static_assert(aDefaultCutoffAccessTime == kSecPerWeek * PR_USEC_PER_SEC);
   8107 
   8108    // Calculate cutoff time (one week ago). PR_Now() returns microseconds
   8109    // since epoch, so this cannot realistically overflow.
   8110    const int64_t cutoffTime = PR_Now() - aDefaultCutoffAccessTime;
   8111 
   8112 #ifdef DEBUG
   8113    // Verify that origins being cleared meet our criteria:
   8114    // non-persisted, zero usage, and outside cutoff window
   8115    auto checker = [&self = *this, cutoffTime](const auto& doomedOriginInfo) {
   8116      MutexAutoLock lock(self.mQuotaMutex);
   8117      MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
   8118      MOZ_ASSERT(doomedOriginInfo->LockedUsage() == 0);
   8119      MOZ_ASSERT(doomedOriginInfo->LockedAccessTime() < cutoffTime);
   8120    };
   8121 #else
   8122    auto checker = [](const auto&) {};
   8123 #endif
   8124 
   8125    const size_t maxOriginsToClear = StaticPrefs::
   8126        dom_quotaManager_temporaryStorage_maxOriginsToClearDuringCleanup();
   8127 
   8128    ClearOrigins(GetOriginInfosWithZeroUsage(Some(cutoffTime)),
   8129                 std::move(checker), Some(maxOriginsToClear));
   8130  }
   8131 
   8132  // Evicting origins that exceed their group limit also affects the global
   8133  // temporary storage usage, so these steps have to be taken sequentially.
   8134  // Combining them doesn't seem worth the added complexity.
   8135 
   8136 #ifdef DEBUG
   8137  auto nonPersistedChecker = [&self = *this](const auto& doomedOriginInfo) {
   8138    MutexAutoLock lock(self.mQuotaMutex);
   8139    MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
   8140  };
   8141 #else
   8142  auto nonPersistedChecker = [](const auto&) {};
   8143 #endif
   8144 
   8145  ClearOrigins(GetOriginInfosExceedingGroupLimit(), nonPersistedChecker);
   8146  ClearOrigins(GetOriginInfosExceedingGlobalLimit(),
   8147               std::move(nonPersistedChecker));
   8148 
   8149  if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
   8150    // If disk space is still low after origin clear, notify storage pressure.
   8151    NotifyStoragePressure(*this, mTemporaryStorageUsage);
   8152  }
   8153 }
   8154 
   8155 void QuotaManager::RecordTemporaryStorageMetrics() {
   8156  AssertIsOnIOThread();
   8157 
   8158  {
   8159    const auto originInfos = GetOriginInfosWithZeroUsage();
   8160 
   8161    auto range = Flatten<OriginInfosFlatTraversable::value_type>(originInfos);
   8162 
   8163    size_t count = std::distance(range.begin(), range.end());
   8164 
   8165    glean::quotamanager_initialize_temporarystorage::
   8166        non_persisted_zero_usage_origins.AccumulateSingleSample(count);
   8167  }
   8168 }
   8169 
   8170 void QuotaManager::DeleteOriginDirectory(
   8171    const OriginMetadata& aOriginMetadata) {
   8172  QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
   8173                 QM_VOID);
   8174 
   8175  nsresult rv = directory->Remove(true);
   8176  if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
   8177    // This should never fail if we've closed all storage connections
   8178    // correctly...
   8179    NS_ERROR("Failed to remove directory!");
   8180  }
   8181 }
   8182 
   8183 void QuotaManager::FinalizeOriginEviction(
   8184    nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
   8185  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   8186 
   8187  auto finalizeOriginEviction = [locks = std::move(aLocks)]() mutable {
   8188    QuotaManager* quotaManager = QuotaManager::Get();
   8189    MOZ_ASSERT(quotaManager);
   8190 
   8191    RefPtr<OriginOperationBase> op = CreateFinalizeOriginEvictionOp(
   8192        WrapMovingNotNull(quotaManager), std::move(locks));
   8193 
   8194    op->RunImmediately();
   8195  };
   8196 
   8197  if (IsOnBackgroundThread()) {
   8198    finalizeOriginEviction();
   8199  } else {
   8200    MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(
   8201        NS_NewRunnableFunction(
   8202            "dom::quota::QuotaManager::FinalizeOriginEviction",
   8203            std::move(finalizeOriginEviction)),
   8204        NS_DISPATCH_NORMAL));
   8205  }
   8206 }
   8207 
   8208 Result<Ok, nsresult> QuotaManager::ArchiveOrigins(
   8209    const nsTArray<FullOriginMetadata>& aFullOriginMetadatas) {
   8210  AssertIsOnIOThread();
   8211  MOZ_ASSERT(!aFullOriginMetadatas.IsEmpty());
   8212 
   8213  QM_TRY_INSPECT(const auto& storageArchivesDir,
   8214                 QM_NewLocalFile(*mStorageArchivesPath));
   8215 
   8216  // Create another subdir, so once we decide to remove all temporary archives,
   8217  // we can remove only the subdir and the parent directory can still be used
   8218  // for something else or similar in future. Otherwise, we would have to
   8219  // figure out a new name for it.
   8220  QM_TRY(MOZ_TO_RESULT(storageArchivesDir->Append(u"0"_ns)));
   8221 
   8222  PRExplodedTime now;
   8223  PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
   8224 
   8225  const auto dateStr =
   8226      nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32, now.tm_year,
   8227                      now.tm_month + 1, now.tm_mday);
   8228 
   8229  QM_TRY_INSPECT(
   8230      const auto& storageArchiveDir,
   8231      CloneFileAndAppend(*storageArchivesDir, NS_ConvertASCIItoUTF16(dateStr)));
   8232 
   8233  QM_TRY(MOZ_TO_RESULT(
   8234      storageArchiveDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700)));
   8235 
   8236  QM_TRY_INSPECT(const auto& defaultStorageArchiveDir,
   8237                 CloneFileAndAppend(*storageArchiveDir,
   8238                                    nsLiteralString(DEFAULT_DIRECTORY_NAME)));
   8239 
   8240  QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir,
   8241                 CloneFileAndAppend(*storageArchiveDir,
   8242                                    nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
   8243 
   8244  for (const auto& fullOriginMetadata : aFullOriginMetadatas) {
   8245    MOZ_ASSERT(
   8246        IsBestEffortPersistenceType(fullOriginMetadata.mPersistenceType));
   8247 
   8248    QM_TRY_INSPECT(const auto& directory,
   8249                   GetOriginDirectory(fullOriginMetadata));
   8250 
   8251    // The origin could have been removed, for example due to corruption.
   8252    QM_TRY_INSPECT(
   8253        const auto& moved,
   8254        QM_OR_ELSE_WARN_IF(
   8255            // Expression.
   8256            MOZ_TO_RESULT(
   8257                directory->MoveTo(fullOriginMetadata.mPersistenceType ==
   8258                                          PERSISTENCE_TYPE_DEFAULT
   8259                                      ? defaultStorageArchiveDir
   8260                                      : temporaryStorageArchiveDir,
   8261                                  u""_ns))
   8262                .map([](Ok) { return true; }),
   8263            // Predicate.
   8264            ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
   8265            // Fallback.
   8266            ErrToOk<false>));
   8267 
   8268    if (moved) {
   8269      RemoveQuotaForOrigin(fullOriginMetadata.mPersistenceType,
   8270                           fullOriginMetadata);
   8271 
   8272      RemoveTemporaryOrigin(fullOriginMetadata);
   8273    }
   8274  }
   8275 
   8276  return Ok{};
   8277 }
   8278 
   8279 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
   8280  AssertIsOnIOThread();
   8281 
   8282  // Do not parse this sanitized origin string, if we already parsed it.
   8283  return mValidOrigins.LookupOrInsertWith(
   8284      aSanitizedOrigin, [&aSanitizedOrigin] {
   8285        nsCString spec;
   8286        OriginAttributes attrs;
   8287        nsCString originalSuffix;
   8288        const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec,
   8289                                                      &attrs, originalSuffix);
   8290 
   8291        return result == OriginParser::ValidOrigin;
   8292      });
   8293 }
   8294 
   8295 void QuotaManager::AddTemporaryOrigin(
   8296    const FullOriginMetadata& aFullOriginMetadata) {
   8297  AssertIsOnIOThread();
   8298  MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
   8299 
   8300  auto ioThreadData = mIOThreadAccessible.Access();
   8301 
   8302  auto& array = ioThreadData->mAllTemporaryOrigins.LookupOrInsert(
   8303      aFullOriginMetadata.mGroup);
   8304 
   8305  DebugOnly<bool> containsOrigin = array.Contains(
   8306      aFullOriginMetadata, [](const auto& aLeft, const auto& aRight) {
   8307        if (aLeft.mPersistenceType == aRight.mPersistenceType) {
   8308          return Compare(aLeft.mOrigin, aRight.mOrigin);
   8309        }
   8310        return aLeft.mPersistenceType > aRight.mPersistenceType ? 1 : -1;
   8311      });
   8312  MOZ_ASSERT(!containsOrigin);
   8313 
   8314  array.AppendElement(aFullOriginMetadata);
   8315 
   8316  if (IsThumbnailPrivateIdentityIdKnown() &&
   8317      IsUserContextSuffix(aFullOriginMetadata.mSuffix,
   8318                          GetThumbnailPrivateIdentityId())) {
   8319    AssertNoOverflow(
   8320        ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1);
   8321    ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount++;
   8322  }
   8323 }
   8324 
   8325 void QuotaManager::RemoveTemporaryOrigin(
   8326    const OriginMetadata& aOriginMetadata) {
   8327  AssertIsOnIOThread();
   8328  MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
   8329 
   8330  auto ioThreadData = mIOThreadAccessible.Access();
   8331 
   8332  auto entry =
   8333      ioThreadData->mAllTemporaryOrigins.Lookup(aOriginMetadata.mGroup);
   8334  if (!entry) {
   8335    return;
   8336  }
   8337 
   8338  auto& array = *entry;
   8339 
   8340  DebugOnly<size_t> count =
   8341      array.RemoveElementsBy([&aOriginMetadata](const auto& originMetadata) {
   8342        return originMetadata.mPersistenceType ==
   8343                   aOriginMetadata.mPersistenceType &&
   8344               originMetadata.mOrigin == aOriginMetadata.mOrigin;
   8345      });
   8346  MOZ_ASSERT(count <= 1);
   8347 
   8348  if (array.IsEmpty()) {
   8349    entry.Remove();
   8350  }
   8351 
   8352  if (IsThumbnailPrivateIdentityIdKnown() &&
   8353      IsUserContextSuffix(aOriginMetadata.mSuffix,
   8354                          GetThumbnailPrivateIdentityId())) {
   8355    AssertNoUnderflow(
   8356        ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1);
   8357    ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount--;
   8358  }
   8359 }
   8360 
   8361 void QuotaManager::RemoveTemporaryOrigins(PersistenceType aPersistenceType) {
   8362  AssertIsOnIOThread();
   8363  MOZ_ASSERT(IsBestEffortPersistenceType(aPersistenceType));
   8364 
   8365  auto ioThreadData = mIOThreadAccessible.Access();
   8366 
   8367  for (auto iter = ioThreadData->mAllTemporaryOrigins.Iter(); !iter.Done();
   8368       iter.Next()) {
   8369    auto& array = iter.Data();
   8370 
   8371    uint32_t thumbnailPrivateIdentityTemporaryOriginCount = 0;
   8372 
   8373    array.RemoveElementsBy([this, aPersistenceType,
   8374                            &thumbnailPrivateIdentityTemporaryOriginCount](
   8375                               const auto& originMetadata) {
   8376      const bool match = originMetadata.mPersistenceType == aPersistenceType;
   8377      if (!match) {
   8378        return false;
   8379      }
   8380 
   8381      if (IsThumbnailPrivateIdentityIdKnown() &&
   8382          IsUserContextSuffix(originMetadata.mSuffix,
   8383                              GetThumbnailPrivateIdentityId())) {
   8384        AssertNoOverflow(thumbnailPrivateIdentityTemporaryOriginCount, 1);
   8385        thumbnailPrivateIdentityTemporaryOriginCount++;
   8386      }
   8387 
   8388      return true;
   8389    });
   8390 
   8391    if (array.IsEmpty()) {
   8392      iter.Remove();
   8393    }
   8394 
   8395    AssertNoUnderflow(
   8396        ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount,
   8397        thumbnailPrivateIdentityTemporaryOriginCount);
   8398    ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount -=
   8399        thumbnailPrivateIdentityTemporaryOriginCount;
   8400  }
   8401 }
   8402 
   8403 void QuotaManager::RemoveTemporaryOrigins() {
   8404  AssertIsOnIOThread();
   8405 
   8406  auto ioThreadData = mIOThreadAccessible.Access();
   8407 
   8408  ioThreadData->mAllTemporaryOrigins.Clear();
   8409  ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount = 0;
   8410 }
   8411 
   8412 PrincipalMetadataArray QuotaManager::GetAllTemporaryGroups() const {
   8413  AssertIsOnIOThread();
   8414 
   8415  auto ioThreadData = mIOThreadAccessible.Access();
   8416 
   8417  PrincipalMetadataArray principalMetadataArray;
   8418 
   8419  std::transform(ioThreadData->mAllTemporaryOrigins.Values().cbegin(),
   8420                 ioThreadData->mAllTemporaryOrigins.Values().cend(),
   8421                 MakeBackInserter(principalMetadataArray),
   8422                 [](const auto& array) {
   8423                   MOZ_ASSERT(!array.IsEmpty());
   8424 
   8425                   // All items in the array have the same PrincipalMetadata,
   8426                   // so we can use any item to get it.
   8427                   return array[0];
   8428                 });
   8429 
   8430  return principalMetadataArray;
   8431 }
   8432 
   8433 OriginMetadataArray QuotaManager::GetAllTemporaryOrigins() const {
   8434  AssertIsOnIOThread();
   8435 
   8436  auto ioThreadData = mIOThreadAccessible.Access();
   8437 
   8438  OriginMetadataArray originMetadataArray;
   8439 
   8440  for (auto iter = ioThreadData->mAllTemporaryOrigins.ConstIter(); !iter.Done();
   8441       iter.Next()) {
   8442    const auto& array = iter.Data();
   8443 
   8444    for (const auto& originMetadata : array) {
   8445      originMetadataArray.AppendElement(originMetadata);
   8446    }
   8447  }
   8448 
   8449  return originMetadataArray;
   8450 }
   8451 
   8452 uint32_t QuotaManager::ThumbnailPrivateIdentityTemporaryOriginCount() const {
   8453  AssertIsOnIOThread();
   8454  MOZ_ASSERT(IsThumbnailPrivateIdentityIdKnown());
   8455 
   8456  return mIOThreadAccessible.Access()
   8457      ->mThumbnailPrivateIdentityTemporaryOriginCount;
   8458 }
   8459 
   8460 void QuotaManager::NoteInitializedOrigin(PersistenceType aPersistenceType,
   8461                                         const nsACString& aOrigin) {
   8462  AssertIsOnOwningThread();
   8463 
   8464  auto& boolArray = mInitializedOrigins.LookupOrInsertWith(aOrigin, []() {
   8465    BoolArray boolArray;
   8466    boolArray.AppendElements(PERSISTENCE_TYPE_INVALID);
   8467    std::fill(boolArray.begin(), boolArray.end(), false);
   8468    return boolArray;
   8469  });
   8470 
   8471  boolArray[aPersistenceType] = true;
   8472 }
   8473 
   8474 void QuotaManager::NoteUninitializedOrigins(
   8475    const OriginMetadataArray& aOriginMetadataArray) {
   8476  AssertIsOnOwningThread();
   8477 
   8478  for (const auto& originMetadata : aOriginMetadataArray) {
   8479    auto entry = mInitializedOrigins.Lookup(originMetadata.mOrigin);
   8480    if (!entry) {
   8481      return;
   8482    }
   8483 
   8484    auto& boolArray = *entry;
   8485 
   8486    if (boolArray[originMetadata.mPersistenceType]) {
   8487      boolArray[originMetadata.mPersistenceType] = false;
   8488 
   8489      if (std::all_of(boolArray.begin(), boolArray.end(),
   8490                      [](bool entry) { return !entry; })) {
   8491        entry.Remove();
   8492      }
   8493    }
   8494  }
   8495 }
   8496 
   8497 void QuotaManager::NoteUninitializedRepository(
   8498    PersistenceType aPersistenceType) {
   8499  AssertIsOnOwningThread();
   8500 
   8501  for (auto iter = mInitializedOrigins.Iter(); !iter.Done(); iter.Next()) {
   8502    auto& boolArray = iter.Data();
   8503 
   8504    if (boolArray[aPersistenceType]) {
   8505      boolArray[aPersistenceType] = false;
   8506 
   8507      if (std::all_of(boolArray.begin(), boolArray.end(),
   8508                      [](bool entry) { return !entry; })) {
   8509        iter.Remove();
   8510      }
   8511    }
   8512  }
   8513 }
   8514 
   8515 bool QuotaManager::IsOriginInitialized(PersistenceType aPersistenceType,
   8516                                       const nsACString& aOrigin) const {
   8517  AssertIsOnOwningThread();
   8518 
   8519  const auto entry = mInitializedOrigins.Lookup(aOrigin);
   8520 
   8521  return entry && (*entry)[aPersistenceType];
   8522 }
   8523 
   8524 void QuotaManager::NoteInitializedClient(PersistenceType aPersistenceType,
   8525                                         const nsACString& aOrigin,
   8526                                         Client::Type aClientType) {
   8527  AssertIsOnOwningThread();
   8528 
   8529  auto& bitSetArray = mInitializedClients.LookupOrInsertWith(aOrigin, []() {
   8530    BitSetArray bitSetArray;
   8531    bitSetArray.AppendElements(PERSISTENCE_TYPE_INVALID);
   8532    return bitSetArray;
   8533  });
   8534 
   8535  bitSetArray[aPersistenceType][aClientType] = true;
   8536 }
   8537 
   8538 void QuotaManager::NoteUninitializedClients(
   8539    const ClientMetadataArray& aClientMetadataArray) {
   8540  AssertIsOnOwningThread();
   8541 
   8542  for (const auto& clientMetadata : aClientMetadataArray) {
   8543    auto entry = mInitializedClients.Lookup(clientMetadata.mOrigin);
   8544    if (!entry) {
   8545      return;
   8546    }
   8547 
   8548    auto& bitSetArray = *entry;
   8549 
   8550    if (bitSetArray[clientMetadata.mPersistenceType]
   8551                   [clientMetadata.mClientType]) {
   8552      bitSetArray[clientMetadata.mPersistenceType][clientMetadata.mClientType] =
   8553          false;
   8554 
   8555      if (std::all_of(bitSetArray.begin(), bitSetArray.end(),
   8556                      [](const auto& entry) { return entry.IsEmpty(); })) {
   8557        entry.Remove();
   8558      }
   8559    }
   8560  }
   8561 }
   8562 
   8563 void QuotaManager::NoteUninitializedClients(
   8564    const OriginMetadataArray& aOriginMetadataArray) {
   8565  AssertIsOnOwningThread();
   8566 
   8567  for (const auto& originMetadata : aOriginMetadataArray) {
   8568    auto entry = mInitializedClients.Lookup(originMetadata.mOrigin);
   8569    if (!entry) {
   8570      return;
   8571    }
   8572 
   8573    auto& bitSetArray = *entry;
   8574 
   8575    bitSetArray[originMetadata.mPersistenceType].ResetAll();
   8576 
   8577    if (std::all_of(bitSetArray.begin(), bitSetArray.end(),
   8578                    [](const auto& entry) { return entry.IsEmpty(); })) {
   8579      entry.Remove();
   8580    }
   8581  }
   8582 }
   8583 
   8584 void QuotaManager::NoteUninitializedClients(PersistenceType aPersistenceType) {
   8585  AssertIsOnOwningThread();
   8586 
   8587  for (auto iter = mInitializedClients.Iter(); !iter.Done(); iter.Next()) {
   8588    auto& bitSetArray = iter.Data();
   8589 
   8590    bitSetArray[aPersistenceType].ResetAll();
   8591 
   8592    if (std::all_of(bitSetArray.begin(), bitSetArray.end(),
   8593                    [](const auto& entry) { return entry.IsEmpty(); })) {
   8594      iter.Remove();
   8595    }
   8596  }
   8597 }
   8598 
   8599 bool QuotaManager::IsClientInitialized(PersistenceType aPersistenceType,
   8600                                       const nsACString& aOrigin,
   8601                                       Client::Type aClientType) const {
   8602  AssertIsOnOwningThread();
   8603 
   8604  const auto entry = mInitializedClients.Lookup(aOrigin);
   8605 
   8606  return entry && (*entry)[aPersistenceType][aClientType];
   8607 }
   8608 
   8609 Result<nsCString, nsresult> QuotaManager::EnsureStorageOriginFromOrigin(
   8610    const nsACString& aOrigin) {
   8611  MutexAutoLock lock(mQuotaMutex);
   8612 
   8613  QM_TRY_UNWRAP(
   8614      auto storageOrigin,
   8615      mOriginToStorageOriginMap.TryLookupOrInsertWith(
   8616          aOrigin, [this, &aOrigin]() -> Result<nsCString, nsresult> {
   8617            OriginAttributes originAttributes;
   8618 
   8619            nsCString originNoSuffix;
   8620            QM_TRY(MOZ_TO_RESULT(
   8621                originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix)));
   8622 
   8623            nsCOMPtr<nsIURI> uri;
   8624            QM_TRY(MOZ_TO_RESULT(
   8625                NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
   8626                    .SetSpec(originNoSuffix)
   8627                    .SetScheme(kUUIDOriginScheme)
   8628                    .SetHost(NSID_TrimBracketsASCII(nsID::GenerateUUID()))
   8629                    .SetPort(-1)
   8630                    .Finalize(uri)));
   8631 
   8632            nsCOMPtr<nsIPrincipal> principal =
   8633                BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{});
   8634            QM_TRY(MOZ_TO_RESULT(principal));
   8635 
   8636            QM_TRY_UNWRAP(auto origin,
   8637                          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   8638                              nsAutoCString, principal, GetOrigin));
   8639 
   8640            mStorageOriginToOriginMap.WithEntryHandle(
   8641                origin,
   8642                [&aOrigin](auto entryHandle) { entryHandle.Insert(aOrigin); });
   8643 
   8644            return nsCString(std::move(origin));
   8645          }));
   8646 
   8647  return nsCString(std::move(storageOrigin));
   8648 }
   8649 
   8650 Result<nsCString, nsresult> QuotaManager::GetOriginFromStorageOrigin(
   8651    const nsACString& aStorageOrigin) {
   8652  MutexAutoLock lock(mQuotaMutex);
   8653 
   8654  auto maybeOrigin = mStorageOriginToOriginMap.MaybeGet(aStorageOrigin);
   8655  if (maybeOrigin.isNothing()) {
   8656    return Err(NS_ERROR_FAILURE);
   8657  }
   8658 
   8659  return maybeOrigin.ref();
   8660 }
   8661 
   8662 int64_t QuotaManager::GenerateDirectoryLockId() {
   8663  const int64_t directorylockId = mNextDirectoryLockId;
   8664 
   8665  if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1;
   8666      result.isValid()) {
   8667    mNextDirectoryLockId = result.value();
   8668  } else {
   8669    NS_WARNING("Quota manager has run out of ids for directory locks!");
   8670 
   8671    // There's very little chance for this to happen given the max size of
   8672    // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
   8673    // to zero since such old directory locks shouldn't exist anymore.
   8674    mNextDirectoryLockId = 0;
   8675  }
   8676 
   8677  // TODO: Maybe add an assertion here to check that there is no existing
   8678  //       directory lock with given id.
   8679 
   8680  return directorylockId;
   8681 }
   8682 
   8683 template <typename UpdateCallback>
   8684 void QuotaManager::RegisterClientDirectoryLockHandle(
   8685    const OriginMetadata& aOriginMetadata, UpdateCallback&& aUpdateCallback) {
   8686  AssertIsOnOwningThread();
   8687 
   8688  auto backgroundThreadData = mBackgroundThreadAccessible.Access();
   8689 
   8690  auto& openClientDirectoryInfo =
   8691      backgroundThreadData->mOpenClientDirectoryInfos.LookupOrInsert(
   8692          aOriginMetadata.GetCompositeKey());
   8693 
   8694  openClientDirectoryInfo.IncreaseClientDirectoryLockHandleCount();
   8695 
   8696  bool firstHandle =
   8697      (openClientDirectoryInfo.ClientDirectoryLockHandleCount() == 1);
   8698 
   8699  if (firstHandle) {
   8700    std::forward<UpdateCallback>(aUpdateCallback)(openClientDirectoryInfo);
   8701  }
   8702 }
   8703 
   8704 template <typename Callback>
   8705 auto QuotaManager::WithOpenClientDirectoryInfo(
   8706    const OriginMetadata& aOriginMetadata, Callback&& aCallback)
   8707    -> std::invoke_result_t<Callback, OpenClientDirectoryInfo&> {
   8708  AssertIsOnOwningThread();
   8709 
   8710  auto backgroundThreadData = mBackgroundThreadAccessible.Access();
   8711 
   8712  auto entry = backgroundThreadData->mOpenClientDirectoryInfos.Lookup(
   8713      aOriginMetadata.GetCompositeKey());
   8714  MOZ_ASSERT(entry);
   8715 
   8716  auto& openClientDirectoryInfo = entry.Data();
   8717 
   8718  return std::forward<Callback>(aCallback)(openClientDirectoryInfo);
   8719 }
   8720 
   8721 template <typename UpdateCallback>
   8722 void QuotaManager::UnregisterClientDirectoryLockHandle(
   8723    const OriginMetadata& aOriginMetadata, UpdateCallback&& aUpdateCallback) {
   8724  AssertIsOnOwningThread();
   8725 
   8726  auto backgroundThreadData = mBackgroundThreadAccessible.Access();
   8727 
   8728  auto entry = backgroundThreadData->mOpenClientDirectoryInfos.Lookup(
   8729      aOriginMetadata.GetCompositeKey());
   8730  MOZ_ASSERT(entry);
   8731 
   8732  auto& openClientDirectoryInfo = entry.Data();
   8733 
   8734  openClientDirectoryInfo.DecreaseClientDirectoryLockHandleCount();
   8735 
   8736  bool lastHandle =
   8737      openClientDirectoryInfo.ClientDirectoryLockHandleCount() == 0;
   8738 
   8739  if (lastHandle) {
   8740    std::forward<UpdateCallback>(aUpdateCallback)(openClientDirectoryInfo);
   8741 
   8742    entry.Remove();
   8743  }
   8744 }
   8745 
   8746 void QuotaManager::ClientDirectoryLockHandleDestroy(
   8747    ClientDirectoryLockHandle& aHandle) {
   8748  AssertIsOnOwningThread();
   8749  MOZ_ASSERT(aHandle);
   8750 
   8751  if (!aHandle.IsRegistered()) {
   8752    return;
   8753  }
   8754 
   8755  const OriginMetadata originMetadata = aHandle->OriginMetadata();
   8756 
   8757  UnregisterClientDirectoryLockHandle(
   8758      originMetadata, [&self = *this, &originMetadata](
   8759                          OpenClientDirectoryInfo& aOpenClientDirectoryInfo) {
   8760        if (originMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT &&
   8761            aOpenClientDirectoryInfo.HasLastAccessDirectoryLock()) {
   8762          self.SaveOriginAccessTime(
   8763              originMetadata,
   8764              aOpenClientDirectoryInfo.ForgetLastAccessDirectoryLock());
   8765        }
   8766      });
   8767 
   8768  aHandle.SetRegistered(false);
   8769 }
   8770 
   8771 template <typename Func>
   8772 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
   8773                                         Func&& aFunc)
   8774    -> std::invoke_result_t<Func, const FirstInitializationAttempt<
   8775                                      Initialization, StringGenerator>&> {
   8776  return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
   8777                                      std::forward<Func>(aFunc));
   8778 }
   8779 
   8780 template <typename Func>
   8781 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
   8782                                         const nsACString& aContext,
   8783                                         Func&& aFunc)
   8784    -> std::invoke_result_t<Func, const FirstInitializationAttempt<
   8785                                      Initialization, StringGenerator>&> {
   8786  return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
   8787                                      aContext, std::forward<Func>(aFunc));
   8788 }
   8789 
   8790 template <typename Func>
   8791 auto QuotaManager::ExecuteGroupInitialization(
   8792    const nsACString& aGroup, const GroupInitialization aInitialization,
   8793    const nsACString& aContext, Func&& aFunc)
   8794    -> std::invoke_result_t<Func, const FirstInitializationAttempt<
   8795                                      Initialization, StringGenerator>&> {
   8796  return quota::ExecuteInitialization(
   8797      mInitializationInfo.MutableGroupInitializationInfoRef(
   8798          aGroup, CreateIfNonExistent{}),
   8799      aInitialization, aContext, std::forward<Func>(aFunc));
   8800 }
   8801 
   8802 template <typename Func>
   8803 auto QuotaManager::ExecuteOriginInitialization(
   8804    const nsACString& aOrigin, const OriginInitialization aInitialization,
   8805    const nsACString& aContext, Func&& aFunc)
   8806    -> std::invoke_result_t<Func, const FirstInitializationAttempt<
   8807                                      Initialization, StringGenerator>&> {
   8808  return quota::ExecuteInitialization(
   8809      mInitializationInfo.MutableOriginInitializationInfoRef(
   8810          aOrigin, CreateIfNonExistent{}),
   8811      aInitialization, aContext, std::forward<Func>(aFunc));
   8812 }
   8813 
   8814 void QuotaManager::IncreaseTotalDirectoryIterations() {
   8815  AssertIsOnIOThread();
   8816 
   8817  auto ioThreadData = mIOThreadAccessible.Access();
   8818 
   8819  AssertNoOverflow(ioThreadData->mTotalDirectoryIterations, 1);
   8820  ioThreadData->mTotalDirectoryIterations++;
   8821 }
   8822 
   8823 void QuotaManager::IncreaseSaveOriginAccessTimeCount() {
   8824  AssertIsOnOwningThread();
   8825 
   8826  auto backgroundThreadData = mBackgroundThreadAccessible.Access();
   8827 
   8828  AssertNoOverflow(backgroundThreadData->mSaveOriginAccessTimeCount, 1);
   8829  backgroundThreadData->mSaveOriginAccessTimeCount++;
   8830 }
   8831 
   8832 void QuotaManager::IncreaseSaveOriginAccessTimeCountInternal() {
   8833  AssertIsOnIOThread();
   8834 
   8835  auto ioThreadData = mIOThreadAccessible.Access();
   8836 
   8837  AssertNoOverflow(ioThreadData->mSaveOriginAccessTimeCount, 1);
   8838  ioThreadData->mSaveOriginAccessTimeCount++;
   8839 }
   8840 
   8841 /*******************************************************************************
   8842 * Local class implementations
   8843 ******************************************************************************/
   8844 
   8845 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
   8846                                           uint64_t aMinSizeToBeFreed)
   8847    : Runnable("dom::quota::CollectOriginsHelper"),
   8848      mMinSizeToBeFreed(aMinSizeToBeFreed),
   8849      mMutex(aMutex),
   8850      mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
   8851      mSizeToBeFreed(0),
   8852      mWaiting(true) {
   8853  MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
   8854  mMutex.AssertCurrentThreadOwns();
   8855 }
   8856 
   8857 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
   8858    nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
   8859  MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
   8860  mMutex.AssertCurrentThreadOwns();
   8861 
   8862  while (mWaiting) {
   8863    mCondVar.Wait();
   8864  }
   8865 
   8866  mLocks.SwapElements(aLocks);
   8867  return mSizeToBeFreed;
   8868 }
   8869 
   8870 NS_IMETHODIMP
   8871 CollectOriginsHelper::Run() {
   8872  AssertIsOnBackgroundThread();
   8873 
   8874  QuotaManager* quotaManager = QuotaManager::Get();
   8875  NS_ASSERTION(quotaManager, "Shouldn't be null!");
   8876 
   8877  // We use extra stack vars here to avoid race detector warnings (the same
   8878  // memory accessed with and without the lock held).
   8879  nsTArray<RefPtr<OriginDirectoryLock>> locks;
   8880  uint64_t sizeToBeFreed =
   8881      quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
   8882 
   8883  MutexAutoLock lock(mMutex);
   8884 
   8885  NS_ASSERTION(mWaiting, "Huh?!");
   8886 
   8887  mLocks.SwapElements(locks);
   8888  mSizeToBeFreed = sizeToBeFreed;
   8889  mWaiting = false;
   8890  mCondVar.Notify();
   8891 
   8892  return NS_OK;
   8893 }
   8894 
   8895 TimeStamp RecordTimeDeltaHelper::Start() {
   8896  MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
   8897 
   8898  // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
   8899  // gLastOSWake is set, then this time duration would still be recorded with
   8900  // key "Normal". We are assumming this is rather rare to happen.
   8901  mStartTime.init(TimeStamp::Now());
   8902  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
   8903 
   8904  return *mStartTime;
   8905 }
   8906 
   8907 TimeStamp RecordTimeDeltaHelper::End() {
   8908  MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
   8909 
   8910  mEndTime.init(TimeStamp::Now());
   8911  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
   8912 
   8913  return *mEndTime;
   8914 }
   8915 
   8916 NS_IMETHODIMP
   8917 RecordTimeDeltaHelper::Run() {
   8918  MOZ_ASSERT(NS_IsMainThread());
   8919 
   8920  if (mInitializedTime.isSome()) {
   8921    // Labels for glean::dom_quota::info_load_time and
   8922    // glean::dom_quota::shutdown_time:
   8923    // Normal: Normal conditions.
   8924    // WasSuspended: There was a OS sleep so that it was suspended.
   8925    // TimeStampErr1: The recorded start time is unexpectedly greater than the
   8926    //                end time.
   8927    // TimeStampErr2: The initialized time for the recording class is unexpectly
   8928    //                greater than the last OS wake time.
   8929    const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() {
   8930      if (wasSuspended) {
   8931        return "WasSuspended"_ns;
   8932      }
   8933 
   8934      // XXX File a bug if we have data for this key.
   8935      // We found negative values in our query in STMO for
   8936      // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
   8937      // because the documentation for TimeStamp::Now() says it returns a
   8938      // monotonically increasing number.
   8939      if (*mStartTime > *mEndTime) {
   8940        return "TimeStampErr1"_ns;
   8941      }
   8942 
   8943      if (*mInitializedTime > gLastOSWake) {
   8944        return "TimeStampErr2"_ns;
   8945      }
   8946 
   8947      return "Normal"_ns;
   8948    }();
   8949 
   8950    mMetric.Get(key).AccumulateRawDuration(*mEndTime - *mStartTime);
   8951 
   8952    return NS_OK;
   8953  }
   8954 
   8955  gLastOSWake = TimeStamp::Now();
   8956  mInitializedTime.init(gLastOSWake);
   8957 
   8958  return NS_OK;
   8959 }
   8960 
   8961 // static
   8962 nsresult StorageOperationBase::CreateDirectoryMetadata(
   8963    nsIFile& aDirectory, int64_t aTimestamp,
   8964    const OriginMetadata& aOriginMetadata) {
   8965  AssertIsOnIOThread();
   8966 
   8967  StorageOriginAttributes groupAttributes;
   8968 
   8969  nsCString groupNoSuffix;
   8970  QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
   8971                                                 groupNoSuffix)),
   8972         NS_ERROR_FAILURE);
   8973 
   8974  nsCString groupPrefix;
   8975  GetJarPrefix(groupAttributes.InIsolatedMozBrowser(), groupPrefix);
   8976 
   8977  nsCString group = groupPrefix + groupNoSuffix;
   8978 
   8979  StorageOriginAttributes originAttributes;
   8980 
   8981  nsCString originNoSuffix;
   8982  QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
   8983                                                  originNoSuffix)),
   8984         NS_ERROR_FAILURE);
   8985 
   8986  nsCString originPrefix;
   8987  GetJarPrefix(originAttributes.InIsolatedMozBrowser(), originPrefix);
   8988 
   8989  nsCString origin = originPrefix + originNoSuffix;
   8990 
   8991  MOZ_ASSERT(groupPrefix == originPrefix);
   8992 
   8993  QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   8994                                       nsCOMPtr<nsIFile>, aDirectory, Clone));
   8995 
   8996  QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME))));
   8997 
   8998  QM_TRY_INSPECT(const auto& stream,
   8999                 GetBinaryOutputStream(*file, FileFlag::Truncate));
   9000  MOZ_ASSERT(stream);
   9001 
   9002  QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
   9003 
   9004  QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(group.get())));
   9005 
   9006  QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(origin.get())));
   9007 
   9008  // Currently unused (used to be isApp).
   9009  QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
   9010 
   9011  QM_TRY(MOZ_TO_RESULT(stream->Flush()));
   9012 
   9013  QM_TRY(MOZ_TO_RESULT(stream->Close()));
   9014 
   9015  QM_TRY(MOZ_TO_RESULT(
   9016      file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME))));
   9017 
   9018  return NS_OK;
   9019 }
   9020 
   9021 // static
   9022 nsresult StorageOperationBase::CreateDirectoryMetadata2(
   9023    nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted,
   9024    const OriginMetadata& aOriginMetadata) {
   9025  AssertIsOnIOThread();
   9026 
   9027  QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9028                                       nsCOMPtr<nsIFile>, aDirectory, Clone));
   9029 
   9030  QM_TRY(
   9031      MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME))));
   9032 
   9033  QM_TRY_INSPECT(const auto& stream,
   9034                 GetBinaryOutputStream(*file, FileFlag::Truncate));
   9035  MOZ_ASSERT(stream);
   9036 
   9037  QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
   9038 
   9039  QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aPersisted)));
   9040 
   9041  // Reserved data 1
   9042  QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
   9043 
   9044  // Reserved data 2
   9045  QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
   9046 
   9047  // The suffix isn't used right now, but we might need it in future. It's
   9048  // a bit of redundancy we can live with given how painful is to upgrade
   9049  // metadata files.
   9050  QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mSuffix.get())));
   9051 
   9052  QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mGroup.get())));
   9053 
   9054  QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mOrigin.get())));
   9055 
   9056  // Currently unused (used to be isApp).
   9057  QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
   9058 
   9059  QM_TRY(MOZ_TO_RESULT(stream->Flush()));
   9060 
   9061  QM_TRY(MOZ_TO_RESULT(stream->Close()));
   9062 
   9063  QM_TRY(MOZ_TO_RESULT(
   9064      file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME))));
   9065 
   9066  return NS_OK;
   9067 }
   9068 
   9069 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
   9070                                                    int64_t& aTimestamp,
   9071                                                    nsACString& aGroup,
   9072                                                    nsACString& aOrigin,
   9073                                                    Nullable<bool>& aIsApp) {
   9074  AssertIsOnIOThread();
   9075  MOZ_ASSERT(aDirectory);
   9076 
   9077  QM_TRY_INSPECT(
   9078      const auto& binaryStream,
   9079      GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
   9080 
   9081  QM_TRY_INSPECT(const uint64_t& timestamp,
   9082                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
   9083 
   9084  QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9085                                        nsCString, binaryStream, ReadCString));
   9086 
   9087  QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9088                                         nsCString, binaryStream, ReadCString));
   9089 
   9090  Nullable<bool> isApp;
   9091  bool value;
   9092  if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
   9093    isApp.SetValue(value);
   9094  }
   9095 
   9096  aTimestamp = timestamp;
   9097  aGroup = group;
   9098  aOrigin = origin;
   9099  aIsApp = std::move(isApp);
   9100  return NS_OK;
   9101 }
   9102 
   9103 nsresult StorageOperationBase::GetDirectoryMetadata2(
   9104    nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
   9105    nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
   9106  AssertIsOnIOThread();
   9107  MOZ_ASSERT(aDirectory);
   9108 
   9109  QM_TRY_INSPECT(const auto& binaryStream,
   9110                 GetBinaryInputStream(*aDirectory,
   9111                                      nsLiteralString(METADATA_V2_FILE_NAME)));
   9112 
   9113  QM_TRY_INSPECT(const uint64_t& timestamp,
   9114                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
   9115 
   9116  QM_TRY_INSPECT(const bool& persisted,
   9117                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
   9118  (void)persisted;
   9119 
   9120  QM_TRY_INSPECT(const bool& reservedData1,
   9121                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
   9122  (void)reservedData1;
   9123 
   9124  QM_TRY_INSPECT(const bool& reservedData2,
   9125                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
   9126  (void)reservedData2;
   9127 
   9128  QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9129                                         nsCString, binaryStream, ReadCString));
   9130 
   9131  QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9132                                        nsCString, binaryStream, ReadCString));
   9133 
   9134  QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9135                                         nsCString, binaryStream, ReadCString));
   9136 
   9137  QM_TRY_INSPECT(const bool& isApp,
   9138                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
   9139 
   9140  aTimestamp = timestamp;
   9141  aSuffix = suffix;
   9142  aGroup = group;
   9143  aOrigin = origin;
   9144  aIsApp = isApp;
   9145  return NS_OK;
   9146 }
   9147 
   9148 int64_t StorageOperationBase::GetOriginLastModifiedTime(
   9149    const OriginProps& aOriginProps) {
   9150  return GetLastModifiedTime(*aOriginProps.mPersistenceType,
   9151                             *aOriginProps.mDirectory);
   9152 }
   9153 
   9154 nsresult StorageOperationBase::RemoveObsoleteOrigin(
   9155    const OriginProps& aOriginProps) {
   9156  AssertIsOnIOThread();
   9157 
   9158  QM_WARNING(
   9159      "Deleting obsolete %s directory that is no longer a legal "
   9160      "origin!",
   9161      NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
   9162 
   9163  QM_TRY(MOZ_TO_RESULT(aOriginProps.mDirectory->Remove(/* recursive */ true)));
   9164 
   9165  return NS_OK;
   9166 }
   9167 
   9168 Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin(
   9169    const OriginProps& aOriginProps) {
   9170  AssertIsOnIOThread();
   9171 
   9172  const nsAString& oldLeafName = aOriginProps.mLeafName;
   9173 
   9174  const auto newLeafName =
   9175      MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin);
   9176 
   9177  if (oldLeafName == newLeafName) {
   9178    return false;
   9179  }
   9180 
   9181  QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(*aOriginProps.mDirectory,
   9182                                               aOriginProps.mTimestamp,
   9183                                               aOriginProps.mOriginMetadata)));
   9184 
   9185  QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
   9186      *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9187      /* aPersisted */ false, aOriginProps.mOriginMetadata)));
   9188 
   9189  QM_TRY_INSPECT(const auto& newFile,
   9190                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9191                     nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent));
   9192 
   9193  QM_TRY(MOZ_TO_RESULT(newFile->Append(newLeafName)));
   9194 
   9195  QM_TRY_INSPECT(const bool& exists,
   9196                 MOZ_TO_RESULT_INVOKE_MEMBER(newFile, Exists));
   9197 
   9198  if (exists) {
   9199    QM_WARNING(
   9200        "Can't rename %s directory to %s, the target already exists, removing "
   9201        "instead of renaming!",
   9202        NS_ConvertUTF16toUTF8(oldLeafName).get(),
   9203        NS_ConvertUTF16toUTF8(newLeafName).get());
   9204  }
   9205 
   9206  QM_TRY(CallWithDelayedRetriesIfAccessDenied(
   9207      [&exists, &aOriginProps, &newLeafName] {
   9208        if (exists) {
   9209          QM_TRY_RETURN(MOZ_TO_RESULT(
   9210              aOriginProps.mDirectory->Remove(/* recursive */ true)));
   9211        }
   9212        QM_TRY_RETURN(MOZ_TO_RESULT(
   9213            aOriginProps.mDirectory->RenameTo(nullptr, newLeafName)));
   9214      },
   9215      StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
   9216      StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
   9217 
   9218  return true;
   9219 }
   9220 
   9221 nsresult StorageOperationBase::ProcessOriginDirectories() {
   9222  AssertIsOnIOThread();
   9223  MOZ_ASSERT(!mOriginProps.IsEmpty());
   9224 
   9225  QuotaManager* quotaManager = QuotaManager::Get();
   9226  MOZ_ASSERT(quotaManager);
   9227 
   9228  for (auto& originProps : mOriginProps) {
   9229    switch (originProps.mType) {
   9230      case OriginProps::eChrome: {
   9231        originProps.mOriginMetadata = {GetInfoForChrome(),
   9232                                       *originProps.mPersistenceType};
   9233        break;
   9234      }
   9235 
   9236      case OriginProps::eContent: {
   9237        nsCOMPtr<nsIURI> uri;
   9238        QM_TRY(
   9239            MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), originProps.mSpec)));
   9240 
   9241        nsCOMPtr<nsIPrincipal> principal =
   9242            BasePrincipal::CreateContentPrincipal(uri, originProps.mAttrs);
   9243        QM_TRY(MOZ_TO_RESULT(principal));
   9244 
   9245        PrincipalInfo principalInfo;
   9246        QM_TRY(
   9247            MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
   9248 
   9249        QM_WARNONLY_TRY_UNWRAP(
   9250            auto valid, MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)));
   9251 
   9252        if (!valid) {
   9253          // Unknown directories during upgrade are allowed. Just warn if we
   9254          // find them.
   9255          UNKNOWN_FILE_WARNING(originProps.mLeafName);
   9256          originProps.mIgnore = true;
   9257          break;
   9258        }
   9259 
   9260        QM_TRY_UNWRAP(
   9261            auto principalMetadata,
   9262            GetInfoFromValidatedPrincipalInfo(*quotaManager, principalInfo));
   9263 
   9264        originProps.mOriginMetadata = {std::move(principalMetadata),
   9265                                       *originProps.mPersistenceType};
   9266 
   9267        break;
   9268      }
   9269 
   9270      case OriginProps::eObsolete: {
   9271        // There's no way to get info for obsolete origins.
   9272        break;
   9273      }
   9274 
   9275      default:
   9276        MOZ_CRASH("Bad type!");
   9277    }
   9278  }
   9279 
   9280  // Don't try to upgrade obsolete origins, remove them right after we detect
   9281  // them.
   9282  for (const auto& originProps : mOriginProps) {
   9283    if (originProps.mType == OriginProps::eObsolete) {
   9284      MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty());
   9285      MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty());
   9286      MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty());
   9287 
   9288      QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps)));
   9289    } else if (!originProps.mIgnore) {
   9290      MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty());
   9291      MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty());
   9292 
   9293      QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps)));
   9294    }
   9295  }
   9296 
   9297  return NS_OK;
   9298 }
   9299 
   9300 // XXX Do the fallible initialization in a separate non-static member function
   9301 // of StorageOperationBase and eventually get rid of this method and use a
   9302 // normal constructor instead.
   9303 template <typename PersistenceTypeFunc>
   9304 nsresult StorageOperationBase::OriginProps::Init(
   9305    PersistenceTypeFunc&& aPersistenceTypeFunc) {
   9306  AssertIsOnIOThread();
   9307 
   9308  QM_TRY_INSPECT(const auto& leafName,
   9309                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, *mDirectory,
   9310                                                   GetLeafName));
   9311 
   9312  // XXX Consider using QuotaManager::ParseOrigin here.
   9313  nsCString spec;
   9314  OriginAttributes attrs;
   9315  nsCString originalSuffix;
   9316  OriginParser::ResultType result = OriginParser::ParseOrigin(
   9317      NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
   9318  if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
   9319    mType = OriginProps::eInvalid;
   9320    return NS_OK;
   9321  }
   9322 
   9323  const auto persistenceType = [&]() -> PersistenceType {
   9324    // XXX We shouldn't continue with initialization if OriginParser returned
   9325    // anything else but ValidOrigin. Otherwise, we have to deal with empty
   9326    // spec when the origin is obsolete, like here. The caller should handle
   9327    // the errors. Until it's fixed, we have to treat obsolete origins as
   9328    // origins with unknown/invalid persistence type.
   9329    if (result != OriginParser::ValidOrigin) {
   9330      return PERSISTENCE_TYPE_INVALID;
   9331    }
   9332    return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec);
   9333  }();
   9334 
   9335  mLeafName = leafName;
   9336  mSpec = spec;
   9337  mAttrs = attrs;
   9338  mOriginalSuffix = originalSuffix;
   9339  mPersistenceType.init(persistenceType);
   9340  if (result == OriginParser::ObsoleteOrigin) {
   9341    mType = eObsolete;
   9342  } else if (mSpec.EqualsLiteral(kChromeOrigin)) {
   9343    mType = eChrome;
   9344  } else {
   9345    mType = eContent;
   9346  }
   9347 
   9348  return NS_OK;
   9349 }
   9350 
   9351 nsresult RepositoryOperationBase::ProcessRepository() {
   9352  AssertIsOnIOThread();
   9353 
   9354 #ifdef DEBUG
   9355  {
   9356    QM_TRY_INSPECT(const bool& exists,
   9357                   MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory, Exists),
   9358                   QM_ASSERT_UNREACHABLE);
   9359    MOZ_ASSERT(exists);
   9360  }
   9361 #endif
   9362 
   9363  QM_TRY(CollectEachFileEntry(
   9364      *mDirectory,
   9365      [](const auto& originFile) -> Result<mozilla::Ok, nsresult> {
   9366        QM_TRY_INSPECT(const auto& leafName,
   9367                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9368                           nsAutoString, originFile, GetLeafName));
   9369 
   9370        // Unknown files during upgrade are allowed. Just warn if we find
   9371        // them.
   9372        if (!IsOSMetadata(leafName)) {
   9373          UNKNOWN_FILE_WARNING(leafName);
   9374        }
   9375 
   9376        return mozilla::Ok{};
   9377      },
   9378      [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> {
   9379        OriginProps originProps(WrapMovingNotNullUnchecked(originDir));
   9380        QM_TRY(MOZ_TO_RESULT(originProps.Init([&self](const auto& aSpec) {
   9381          return self.PersistenceTypeFromSpec(aSpec);
   9382        })));
   9383        // Bypass invalid origins while upgrading
   9384        QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{});
   9385 
   9386        if (originProps.mType != OriginProps::eObsolete) {
   9387          QM_TRY_INSPECT(const bool& removed,
   9388                         MOZ_TO_RESULT_INVOKE_MEMBER(
   9389                             self, PrepareOriginDirectory, originProps));
   9390          if (removed) {
   9391            return mozilla::Ok{};
   9392          }
   9393        }
   9394 
   9395        self.mOriginProps.AppendElement(std::move(originProps));
   9396 
   9397        return mozilla::Ok{};
   9398      }));
   9399 
   9400  if (mOriginProps.IsEmpty()) {
   9401    return NS_OK;
   9402  }
   9403 
   9404  QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
   9405 
   9406  return NS_OK;
   9407 }
   9408 
   9409 template <typename UpgradeMethod>
   9410 nsresult RepositoryOperationBase::MaybeUpgradeClients(
   9411    const OriginProps& aOriginProps, UpgradeMethod aMethod) {
   9412  AssertIsOnIOThread();
   9413  MOZ_ASSERT(aMethod);
   9414 
   9415  QuotaManager* quotaManager = QuotaManager::Get();
   9416  MOZ_ASSERT(quotaManager);
   9417 
   9418  QM_TRY(CollectEachFileEntry(
   9419      *aOriginProps.mDirectory,
   9420      [](const auto& file) -> Result<mozilla::Ok, nsresult> {
   9421        QM_TRY_INSPECT(
   9422            const auto& leafName,
   9423            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName));
   9424 
   9425        if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
   9426          UNKNOWN_FILE_WARNING(leafName);
   9427        }
   9428 
   9429        return mozilla::Ok{};
   9430      },
   9431      [quotaManager, &aMethod,
   9432       &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> {
   9433        QM_TRY_INSPECT(
   9434            const auto& leafName,
   9435            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, dir, GetLeafName));
   9436 
   9437        QM_TRY_INSPECT(const bool& removed,
   9438                       MOZ_TO_RESULT_INVOKE_MEMBER(self, PrepareClientDirectory,
   9439                                                   dir, leafName));
   9440        if (removed) {
   9441          return mozilla::Ok{};
   9442        }
   9443 
   9444        Client::Type clientType;
   9445        bool ok = Client::TypeFromText(leafName, clientType, fallible);
   9446        if (!ok) {
   9447          UNKNOWN_FILE_WARNING(leafName);
   9448          return mozilla::Ok{};
   9449        }
   9450 
   9451        Client* client = quotaManager->GetClient(clientType);
   9452        MOZ_ASSERT(client);
   9453 
   9454        QM_TRY(MOZ_TO_RESULT((client->*aMethod)(dir)));
   9455 
   9456        return mozilla::Ok{};
   9457      }));
   9458 
   9459  return NS_OK;
   9460 }
   9461 
   9462 nsresult RepositoryOperationBase::PrepareClientDirectory(
   9463    nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
   9464  AssertIsOnIOThread();
   9465 
   9466  aRemoved = false;
   9467  return NS_OK;
   9468 }
   9469 
   9470 nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() {
   9471  AssertIsOnIOThread();
   9472  MOZ_ASSERT(mDirectory);
   9473 
   9474  const auto maybeLegacyPersistenceType =
   9475      LegacyPersistenceTypeFromFile(*mDirectory, fallible);
   9476  QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE));
   9477 
   9478  mLegacyPersistenceType.init(maybeLegacyPersistenceType.value());
   9479 
   9480  return NS_OK;
   9481 }
   9482 
   9483 Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType>
   9484 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
   9485    nsIFile& aFile, const fallible_t&) {
   9486  nsAutoString leafName;
   9487  MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName));
   9488 
   9489  if (leafName.Equals(u"persistent"_ns)) {
   9490    return Some(LegacyPersistenceType::Persistent);
   9491  }
   9492 
   9493  if (leafName.Equals(u"temporary"_ns)) {
   9494    return Some(LegacyPersistenceType::Temporary);
   9495  }
   9496 
   9497  return Nothing();
   9498 }
   9499 
   9500 PersistenceType
   9501 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
   9502    const nsCString& aSpec) {
   9503  if (QuotaManager::IsOriginInternal(aSpec)) {
   9504    return PERSISTENCE_TYPE_PERSISTENT;
   9505  }
   9506 
   9507  return PERSISTENCE_TYPE_DEFAULT;
   9508 }
   9509 
   9510 PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
   9511    const nsCString& aSpec) {
   9512  switch (*mLegacyPersistenceType) {
   9513    case LegacyPersistenceType::Persistent:
   9514      return PersistenceTypeFromLegacyPersistentSpec(aSpec);
   9515    case LegacyPersistenceType::Temporary:
   9516      return PERSISTENCE_TYPE_TEMPORARY;
   9517  }
   9518  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
   9519 }
   9520 
   9521 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
   9522    nsIFile* aDirectory) {
   9523  AssertIsOnIOThread();
   9524  MOZ_ASSERT(aDirectory);
   9525 
   9526  QM_TRY_INSPECT(
   9527      const auto& metadataFile,
   9528      CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
   9529 
   9530  QM_TRY_INSPECT(const bool& exists,
   9531                 MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile, Exists));
   9532 
   9533  if (!exists) {
   9534    // Directory structure upgrade needed.
   9535    // Move all files to IDB specific directory.
   9536 
   9537    nsString idbDirectoryName;
   9538    QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)),
   9539           NS_ERROR_FAILURE);
   9540 
   9541    QM_TRY_INSPECT(const auto& idbDirectory,
   9542                   CloneFileAndAppend(*aDirectory, idbDirectoryName));
   9543 
   9544    // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF
   9545    // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the
   9546    // idb directory shouldn't exist during the upgrade and the upgrade runs
   9547    // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok
   9548    // here.
   9549    QM_TRY(QM_OR_ELSE_WARN_IF(
   9550        // Expression.
   9551        MOZ_TO_RESULT(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
   9552        // Predicate.
   9553        IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
   9554        // Fallback.
   9555        ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> {
   9556          QM_TRY_INSPECT(
   9557              const bool& isDirectory,
   9558              MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory, IsDirectory));
   9559 
   9560          QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
   9561 
   9562          return Ok{};
   9563        })));
   9564 
   9565    QM_TRY(CollectEachFile(
   9566        *aDirectory,
   9567        [&idbDirectory, &idbDirectoryName](
   9568            const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
   9569          QM_TRY_INSPECT(const auto& leafName,
   9570                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
   9571                                                           GetLeafName));
   9572 
   9573          if (!leafName.Equals(idbDirectoryName)) {
   9574            QM_TRY(MOZ_TO_RESULT(file->MoveTo(idbDirectory, u""_ns)));
   9575          }
   9576 
   9577          return Ok{};
   9578        }));
   9579 
   9580    QM_TRY(
   9581        MOZ_TO_RESULT(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
   9582  }
   9583 
   9584  return NS_OK;
   9585 }
   9586 
   9587 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
   9588    OriginProps& aOriginProps, bool* aRemoved) {
   9589  AssertIsOnIOThread();
   9590  MOZ_ASSERT(aRemoved);
   9591 
   9592  if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
   9593    QM_TRY(MOZ_TO_RESULT(
   9594        MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get())));
   9595 
   9596    aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
   9597  } else {
   9598    int64_t timestamp;
   9599    nsCString group;
   9600    nsCString origin;
   9601    Nullable<bool> isApp;
   9602 
   9603    QM_WARNONLY_TRY_UNWRAP(
   9604        const auto maybeDirectoryMetadata,
   9605        MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
   9606                                           timestamp, group, origin, isApp)));
   9607    if (!maybeDirectoryMetadata) {
   9608      aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
   9609      aOriginProps.mNeedsRestore = true;
   9610    } else if (!isApp.IsNull()) {
   9611      aOriginProps.mIgnore = true;
   9612    }
   9613  }
   9614 
   9615  *aRemoved = false;
   9616  return NS_OK;
   9617 }
   9618 
   9619 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
   9620    const OriginProps& aOriginProps) {
   9621  AssertIsOnIOThread();
   9622 
   9623  if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
   9624    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
   9625        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9626        aOriginProps.mOriginMetadata)));
   9627 
   9628    // Move internal origins to new persistent storage.
   9629    if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) ==
   9630        PERSISTENCE_TYPE_PERSISTENT) {
   9631      if (!mPermanentStorageDir) {
   9632        QuotaManager* quotaManager = QuotaManager::Get();
   9633        MOZ_ASSERT(quotaManager);
   9634 
   9635        const nsString& permanentStoragePath =
   9636            quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
   9637 
   9638        QM_TRY_UNWRAP(mPermanentStorageDir,
   9639                      QM_NewLocalFile(permanentStoragePath));
   9640      }
   9641 
   9642      const nsAString& leafName = aOriginProps.mLeafName;
   9643 
   9644      QM_TRY_INSPECT(const auto& newDirectory,
   9645                     CloneFileAndAppend(*mPermanentStorageDir, leafName));
   9646 
   9647      QM_TRY_INSPECT(const bool& exists,
   9648                     MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory, Exists));
   9649 
   9650      if (exists) {
   9651        QM_WARNING("Found %s in storage/persistent and storage/permanent !",
   9652                   NS_ConvertUTF16toUTF8(leafName).get());
   9653 
   9654        QM_TRY(MOZ_TO_RESULT(
   9655            aOriginProps.mDirectory->Remove(/* recursive */ true)));
   9656      } else {
   9657        QM_TRY(MOZ_TO_RESULT(
   9658            aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns)));
   9659      }
   9660    }
   9661  } else if (aOriginProps.mNeedsRestore) {
   9662    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
   9663        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9664        aOriginProps.mOriginMetadata)));
   9665  } else if (!aOriginProps.mIgnore) {
   9666    QM_TRY_INSPECT(const auto& file,
   9667                   CloneFileAndAppend(*aOriginProps.mDirectory,
   9668                                      nsLiteralString(METADATA_FILE_NAME)));
   9669 
   9670    QM_TRY_INSPECT(const auto& stream,
   9671                   GetBinaryOutputStream(*file, FileFlag::Append));
   9672 
   9673    MOZ_ASSERT(stream);
   9674 
   9675    // Currently unused (used to be isApp).
   9676    QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
   9677  }
   9678 
   9679  return NS_OK;
   9680 }
   9681 
   9682 nsresult UpgradeStorageHelperBase::Init() {
   9683  AssertIsOnIOThread();
   9684  MOZ_ASSERT(mDirectory);
   9685 
   9686  const auto maybePersistenceType =
   9687      PersistenceTypeFromFile(*mDirectory, fallible);
   9688  QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
   9689 
   9690  mPersistenceType.init(maybePersistenceType.value());
   9691 
   9692  return NS_OK;
   9693 }
   9694 
   9695 PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec(
   9696    const nsCString& aSpec) {
   9697  // There's no moving of origin directories between repositories like in the
   9698  // CreateOrUpgradeDirectoryMetadataHelper
   9699  return *mPersistenceType;
   9700 }
   9701 
   9702 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
   9703    OriginProps& aOriginProps, bool* aRemoved) {
   9704  AssertIsOnIOThread();
   9705  MOZ_ASSERT(aRemoved);
   9706 
   9707  int64_t timestamp;
   9708  nsCString group;
   9709  nsCString origin;
   9710  Nullable<bool> isApp;
   9711 
   9712  QM_WARNONLY_TRY_UNWRAP(
   9713      const auto maybeDirectoryMetadata,
   9714      MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
   9715                                         timestamp, group, origin, isApp)));
   9716  if (!maybeDirectoryMetadata || isApp.IsNull()) {
   9717    aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
   9718    aOriginProps.mNeedsRestore = true;
   9719  } else {
   9720    aOriginProps.mTimestamp = timestamp;
   9721  }
   9722 
   9723  *aRemoved = false;
   9724  return NS_OK;
   9725 }
   9726 
   9727 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
   9728    const OriginProps& aOriginProps) {
   9729  AssertIsOnIOThread();
   9730 
   9731  // This handles changes in origin string generation from nsIPrincipal,
   9732  // especially the change from: appId+inMozBrowser+originNoSuffix
   9733  // to: origin (with origin suffix).
   9734  QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
   9735  if (renamed) {
   9736    return NS_OK;
   9737  }
   9738 
   9739  if (aOriginProps.mNeedsRestore) {
   9740    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
   9741        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9742        aOriginProps.mOriginMetadata)));
   9743  }
   9744 
   9745  QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
   9746      *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9747      /* aPersisted */ false, aOriginProps.mOriginMetadata)));
   9748 
   9749  return NS_OK;
   9750 }
   9751 
   9752 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
   9753    const OriginProps& aOriginProps) {
   9754  AssertIsOnIOThread();
   9755 
   9756  // The Cache API was creating top level morgue directories by accident for
   9757  // a short time in nightly.  This unfortunately prevents all storage from
   9758  // working.  So recover these profiles permanently by removing these corrupt
   9759  // directories as part of this upgrade.
   9760 
   9761  QM_TRY_INSPECT(const auto& morgueDir,
   9762                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   9763                     nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, Clone));
   9764 
   9765  QM_TRY(MOZ_TO_RESULT(morgueDir->Append(u"morgue"_ns)));
   9766 
   9767  QM_TRY_INSPECT(const bool& exists,
   9768                 MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir, Exists));
   9769 
   9770  if (exists) {
   9771    QM_WARNING("Deleting accidental morgue directory!");
   9772 
   9773    QM_TRY(MOZ_TO_RESULT(morgueDir->Remove(/* recursive */ true)));
   9774  }
   9775 
   9776  return NS_OK;
   9777 }
   9778 
   9779 Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
   9780    const OriginProps& aOriginProps) {
   9781  AssertIsOnIOThread();
   9782 
   9783  // TODO: This method was empty for some time due to accidental changes done
   9784  //       in bug 1320404. This led to renaming of origin directories like:
   9785  //         https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
   9786  //       to:
   9787  //         https+++developer.cdn.mozilla.net^inBrowser=1
   9788  //       instead of just removing them.
   9789 
   9790  const nsCString& originalSuffix = aOriginProps.mOriginalSuffix;
   9791  if (!originalSuffix.IsEmpty()) {
   9792    MOZ_ASSERT(originalSuffix[0] == '^');
   9793 
   9794    if (!URLParams::Parse(
   9795            Substring(originalSuffix, 1, originalSuffix.Length() - 1), true,
   9796            [](const nsACString& aName, const nsACString& aValue) {
   9797              if (aName.EqualsLiteral("appId")) {
   9798                return false;
   9799              }
   9800              return true;
   9801            })) {
   9802      QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps)));
   9803 
   9804      return true;
   9805    }
   9806  }
   9807 
   9808  return false;
   9809 }
   9810 
   9811 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
   9812    OriginProps& aOriginProps, bool* aRemoved) {
   9813  AssertIsOnIOThread();
   9814  MOZ_ASSERT(aRemoved);
   9815 
   9816  QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps)));
   9817 
   9818  QM_TRY(MOZ_TO_RESULT(
   9819      MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0)));
   9820 
   9821  QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps));
   9822  if (removed) {
   9823    *aRemoved = true;
   9824    return NS_OK;
   9825  }
   9826 
   9827  int64_t timestamp;
   9828  nsCString group;
   9829  nsCString origin;
   9830  Nullable<bool> isApp;
   9831  QM_WARNONLY_TRY_UNWRAP(
   9832      const auto maybeDirectoryMetadata,
   9833      MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
   9834                                         timestamp, group, origin, isApp)));
   9835  if (!maybeDirectoryMetadata || isApp.IsNull()) {
   9836    aOriginProps.mNeedsRestore = true;
   9837  }
   9838 
   9839  nsCString suffix;
   9840  QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
   9841                         MOZ_TO_RESULT(GetDirectoryMetadata2(
   9842                             aOriginProps.mDirectory.get(), timestamp, suffix,
   9843                             group, origin, isApp.SetValue())));
   9844  if (!maybeDirectoryMetadata2) {
   9845    aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
   9846    aOriginProps.mNeedsRestore2 = true;
   9847  } else {
   9848    aOriginProps.mTimestamp = timestamp;
   9849  }
   9850 
   9851  *aRemoved = false;
   9852  return NS_OK;
   9853 }
   9854 
   9855 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
   9856    const OriginProps& aOriginProps) {
   9857  AssertIsOnIOThread();
   9858 
   9859  // This handles changes in origin string generation from nsIPrincipal,
   9860  // especially the stripping of obsolete origin attributes like addonId.
   9861  QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
   9862  if (renamed) {
   9863    return NS_OK;
   9864  }
   9865 
   9866  if (aOriginProps.mNeedsRestore) {
   9867    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
   9868        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9869        aOriginProps.mOriginMetadata)));
   9870  }
   9871 
   9872  if (aOriginProps.mNeedsRestore2) {
   9873    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
   9874        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9875        /* aPersisted */ false, aOriginProps.mOriginMetadata)));
   9876  }
   9877 
   9878  return NS_OK;
   9879 }
   9880 
   9881 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
   9882    OriginProps& aOriginProps, bool* aRemoved) {
   9883  AssertIsOnIOThread();
   9884  MOZ_ASSERT(aRemoved);
   9885 
   9886  QM_TRY(MOZ_TO_RESULT(
   9887      MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1)));
   9888 
   9889  int64_t timestamp;
   9890  nsCString group;
   9891  nsCString origin;
   9892  Nullable<bool> isApp;
   9893  QM_WARNONLY_TRY_UNWRAP(
   9894      const auto maybeDirectoryMetadata,
   9895      MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
   9896                                         timestamp, group, origin, isApp)));
   9897  if (!maybeDirectoryMetadata || isApp.IsNull()) {
   9898    aOriginProps.mNeedsRestore = true;
   9899  }
   9900 
   9901  nsCString suffix;
   9902  QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
   9903                         MOZ_TO_RESULT(GetDirectoryMetadata2(
   9904                             aOriginProps.mDirectory.get(), timestamp, suffix,
   9905                             group, origin, isApp.SetValue())));
   9906  if (!maybeDirectoryMetadata2) {
   9907    aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
   9908    aOriginProps.mNeedsRestore2 = true;
   9909  } else {
   9910    aOriginProps.mTimestamp = timestamp;
   9911  }
   9912 
   9913  *aRemoved = false;
   9914  return NS_OK;
   9915 }
   9916 
   9917 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
   9918    const OriginProps& aOriginProps) {
   9919  AssertIsOnIOThread();
   9920 
   9921  if (aOriginProps.mNeedsRestore) {
   9922    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
   9923        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9924        aOriginProps.mOriginMetadata)));
   9925  }
   9926 
   9927  if (aOriginProps.mNeedsRestore2) {
   9928    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
   9929        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9930        /* aPersisted */ false, aOriginProps.mOriginMetadata)));
   9931  }
   9932 
   9933  return NS_OK;
   9934 }
   9935 
   9936 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
   9937    OriginProps& aOriginProps, bool* aRemoved) {
   9938  AssertIsOnIOThread();
   9939  MOZ_ASSERT(aRemoved);
   9940 
   9941  QM_TRY(MOZ_TO_RESULT(
   9942      MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2)));
   9943 
   9944  int64_t timestamp;
   9945  nsCString group;
   9946  nsCString origin;
   9947  Nullable<bool> isApp;
   9948  QM_WARNONLY_TRY_UNWRAP(
   9949      const auto maybeDirectoryMetadata,
   9950      MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
   9951                                         timestamp, group, origin, isApp)));
   9952  if (!maybeDirectoryMetadata || isApp.IsNull()) {
   9953    aOriginProps.mNeedsRestore = true;
   9954  }
   9955 
   9956  nsCString suffix;
   9957  QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
   9958                         MOZ_TO_RESULT(GetDirectoryMetadata2(
   9959                             aOriginProps.mDirectory.get(), timestamp, suffix,
   9960                             group, origin, isApp.SetValue())));
   9961  if (!maybeDirectoryMetadata2) {
   9962    aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
   9963    aOriginProps.mNeedsRestore2 = true;
   9964  } else {
   9965    aOriginProps.mTimestamp = timestamp;
   9966  }
   9967 
   9968  *aRemoved = false;
   9969  return NS_OK;
   9970 }
   9971 
   9972 nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
   9973    const OriginProps& aOriginProps) {
   9974  AssertIsOnIOThread();
   9975 
   9976  if (aOriginProps.mNeedsRestore) {
   9977    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
   9978        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9979        aOriginProps.mOriginMetadata)));
   9980  }
   9981 
   9982  if (aOriginProps.mNeedsRestore2) {
   9983    QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
   9984        *aOriginProps.mDirectory, aOriginProps.mTimestamp,
   9985        /* aPersisted */ false, aOriginProps.mOriginMetadata)));
   9986  }
   9987 
   9988  return NS_OK;
   9989 }
   9990 
   9991 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
   9992    nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
   9993  AssertIsOnIOThread();
   9994 
   9995  if (Client::IsDeprecatedClient(aLeafName)) {
   9996    QM_WARNING("Deleting deprecated %s client!",
   9997               NS_ConvertUTF16toUTF8(aLeafName).get());
   9998 
   9999    QM_TRY(MOZ_TO_RESULT(aFile->Remove(true)));
  10000 
  10001    aRemoved = true;
  10002  } else {
  10003    aRemoved = false;
  10004  }
  10005 
  10006  return NS_OK;
  10007 }
  10008 
  10009 nsresult RestoreDirectoryMetadata2Helper::Init() {
  10010  AssertIsOnIOThread();
  10011  MOZ_ASSERT(mDirectory);
  10012 
  10013  nsCOMPtr<nsIFile> parentDir;
  10014  QM_TRY(MOZ_TO_RESULT(mDirectory->GetParent(getter_AddRefs(parentDir))));
  10015 
  10016  const auto maybePersistenceType =
  10017      PersistenceTypeFromFile(*parentDir, fallible);
  10018  QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
  10019 
  10020  mPersistenceType.init(maybePersistenceType.value());
  10021 
  10022  return NS_OK;
  10023 }
  10024 
  10025 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
  10026  OriginProps originProps(WrapMovingNotNull(mDirectory));
  10027  QM_TRY(MOZ_TO_RESULT(originProps.Init(
  10028      [&self = *this](const auto& aSpec) { return *self.mPersistenceType; })));
  10029 
  10030  QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE);
  10031 
  10032  originProps.mTimestamp = GetOriginLastModifiedTime(originProps);
  10033 
  10034  mOriginProps.AppendElement(std::move(originProps));
  10035 
  10036  QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
  10037 
  10038  return NS_OK;
  10039 }
  10040 
  10041 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
  10042    const OriginProps& aOriginProps) {
  10043  AssertIsOnIOThread();
  10044 
  10045  // We don't have any approach to restore aPersisted, so reset it to false.
  10046  QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
  10047      *aOriginProps.mDirectory,
  10048      FullOriginMetadata{
  10049          aOriginProps.mOriginMetadata,
  10050          OriginStateMetadata{
  10051              aOriginProps.mTimestamp,
  10052              /* aLastMaintenanceDate */
  10053              Date::FromTimestamp(aOriginProps.mTimestamp).ToDays(),
  10054              /* aAccessed */ true,
  10055              /* aPersisted */ false},
  10056          ClientUsageArray(), /* aUsage */ 0, kNoQuotaVersion})));
  10057 
  10058  return NS_OK;
  10059 }
  10060 
  10061 }  // namespace mozilla::dom::quota