/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ActorsParent.h" #include "LocalStorageCommon.h" #include "LSObject.h" #include "mozIStorageConnection.h" #include "mozIStorageFunction.h" #include "mozIStorageService.h" #include "mozStorageCID.h" #include "mozStorageHelper.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "mozilla/Unused.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/PBackgroundLSDatabaseParent.h" #include "mozilla/dom/PBackgroundLSObserverParent.h" #include "mozilla/dom/PBackgroundLSRequestParent.h" #include "mozilla/dom/PBackgroundLSSharedTypes.h" #include "mozilla/dom/PBackgroundLSSimpleRequestParent.h" #include "mozilla/dom/PBackgroundLSSnapshotParent.h" #include "mozilla/dom/StorageDBUpdater.h" #include "mozilla/dom/StorageUtils.h" #include "mozilla/dom/quota/OriginScope.h" #include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/QuotaObject.h" #include "mozilla/dom/quota/UsageInfo.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/PBackgroundChild.h" #include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" #include "nsInterfaceHashtable.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" #include "nsISimpleEnumerator.h" #include "nsNetUtil.h" #include "nsRefPtrHashtable.h" #include "ReportInternalError.h" #define DISABLE_ASSERTS_FOR_FUZZING 0 #if DISABLE_ASSERTS_FOR_FUZZING # define ASSERT_UNLESS_FUZZING(...) \ do { \ } while (0) #else # define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) #endif #if defined(MOZ_WIDGET_ANDROID) # define LS_MOBILE #endif namespace mozilla { namespace dom { using namespace mozilla::dom::quota; using namespace mozilla::dom::StorageUtils; using namespace mozilla::ipc; namespace { struct ArchivedOriginInfo; class ArchivedOriginScope; class Connection; class ConnectionThread; class Database; class PrepareDatastoreOp; class PreparedDatastore; class QuotaClient; class Snapshot; typedef nsClassHashtable ArchivedOriginHashtable; /******************************************************************************* * Constants ******************************************************************************/ // Major schema version. Bump for almost everything. const uint32_t kMajorSchemaVersion = 2; // Minor schema version. Should almost always be 0 (maybe bump on release // branches if we have to). const uint32_t kMinorSchemaVersion = 0; // The schema version we store in the SQLite database is a (signed) 32-bit // integer. The major version is left-shifted 4 bits so the max value is // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF. static_assert(kMajorSchemaVersion <= 0xFFFFFFF, "Major version needs to fit in 28 bits."); static_assert(kMinorSchemaVersion <= 0xF, "Minor version needs to fit in 4 bits."); const int32_t kSQLiteSchemaVersion = int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion); // Changing the value here will override the page size of new databases only. // A journal mode change and VACUUM are needed to change existing databases, so // the best way to do that is to use the schema version upgrade mechanism. const uint32_t kSQLitePageSizeOverride = #ifdef LS_MOBILE 512; #else 1024; #endif static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 || (kSQLitePageSizeOverride % 2 == 0 && kSQLitePageSizeOverride >= 512 && kSQLitePageSizeOverride <= 65536), "Must be 0 (disabled) or a power of 2 between 512 and 65536!"); // Set to some multiple of the page size to grow the database in larger chunks. const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2; static_assert(kSQLiteGrowthIncrement >= 0 && kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 && kSQLiteGrowthIncrement < uint32_t(INT32_MAX), "Must be 0 (disabled) or a positive multiple of the page size!"); /** * The database name for LocalStorage data in a per-origin directory. */ #define DATA_FILE_NAME "data.sqlite" /** * The journal corresponding to DATA_FILE_NAME. (We don't use WAL mode.) */ #define JOURNAL_FILE_NAME "data.sqlite-journal" /** * This file contains the current usage of the LocalStorage database as defined * by the mozLength totals of all keys and values for the database, which * differs from the actual size on disk. We store this value in a separate * file as a cache so that we can initialize the QuotaClient faster. * In the future, this file will be eliminated and the information will be * stored in PROFILE/storage.sqlite or similar QuotaManager-wide storage. * * The file contains a binary verification cookie (32-bits) followed by the * actual usage (64-bits). */ #define USAGE_FILE_NAME "usage" /** * Following a QuotaManager idiom, this journal file's existence is a marker * that the usage file was in the process of being updated and is currently * invalid. This file is created prior to updating the usage file and only * deleted after the usage file has been written and closed and any pending * database transactions have been committed. Note that this idiom is expected * to work if Gecko crashes in the middle of a write, but is not expected to be * foolproof in the face of a system crash, as we do not explicitly attempt to * fsync the directory containing the journal file. * * If the journal file is found to exist at origin initialization time, the * usage will be re-computed from the current state of DATA_FILE_NAME. */ #define USAGE_JOURNAL_FILE_NAME "usage-journal" static const uint32_t kUsageFileSize = 12; static const uint32_t kUsageFileCookie = 0x420a420a; /** * How long between the first moment we know we have data to be written on a * `Connection` and when we should actually perform the write. This helps * limit disk churn under silly usage patterns and is historically consistent * with the previous, legacy implementation. * * Note that flushing happens downstream of Snapshot checkpointing and its * batch mechanism which helps avoid wasteful IPC in the case of silly content * code. */ const uint32_t kFlushTimeoutMs = 5000; const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited"; const uint32_t kDefaultOriginLimitKB = 5 * 1024; const uint32_t kDefaultShadowWrites = true; const uint32_t kDefaultSnapshotPrefill = 4096; /** * LocalStorage data limit as determined by summing up the lengths of all string * keys and values. This is consistent with the legacy implementation and other * browser engines. This value should really only ever change in unit testing * where being able to lower it makes it easier for us to test certain edge * cases. */ const char kDefaultQuotaPref[] = "dom.storage.default_quota"; /** * Should all mutations also be reflected in the "shadow" database, which is * the legacy webappsstore.sqlite database. When this is enabled, users can * downgrade their version of Firefox and/or otherwise fall back to the legacy * implementation without loss of data. (Older versions of Firefox will * recognize the presence of ls-archive.sqlite and purge it and the other * LocalStorage directories so privacy is maintained.) */ const char kShadowWritesPref[] = "dom.storage.shadow_writes"; /** * Byte budget for sending data down to the LSSnapshot instance when it is first * created. If there is less data than this (measured by tallying the string * length of the keys and values), all data is sent, otherwise partial data is * sent. See `Snapshot`. */ const char kSnapshotPrefillPref[] = "dom.storage.snapshot_prefill"; /** * The amount of time a PreparedDatastore instance should stick around after a * preload is triggered in order to give time for the page to use LocalStorage * without triggering worst-case synchronous jank. */ const uint32_t kPreparedDatastoreTimeoutMs = 20000; /** * Cold storage for LocalStorage data extracted from webappsstore.sqlite at * LSNG first-run that has not yet been migrated to its own per-origin directory * by use. * * In other words, at first run, LSNG copies the contents of webappsstore.sqlite * into this database. As requests are made for that LocalStorage data, the * contents are removed from this database and placed into per-origin QM * storage. So the contents of this database are always old, unused * LocalStorage data that we can potentially get rid of at some point in the * future. */ #define LS_ARCHIVE_FILE_NAME "ls-archive.sqlite" /** * The legacy LocalStorage database. Its contents are maintained as our * "shadow" database so that LSNG can be disabled without loss of user data. */ #define WEB_APPS_STORE_FILE_NAME "webappsstore.sqlite" // Shadow database Write Ahead Log's maximum size is 512KB const uint32_t kShadowMaxWALSize = 512 * 1024; const uint32_t kShadowJournalSizeLimit = kShadowMaxWALSize * 3; bool IsOnConnectionThread(); void AssertIsOnConnectionThread(); /******************************************************************************* * SQLite functions ******************************************************************************/ int32_t MakeSchemaVersion(uint32_t aMajorSchemaVersion, uint32_t aMinorSchemaVersion) { return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion); } nsCString GetArchivedOriginHashKey(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) { return aOriginSuffix + NS_LITERAL_CSTRING(":") + aOriginNoSuffix; } nsresult CreateTables(mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); // Table `database` nsresult rv = aConnection->ExecuteSimpleSQL( NS_LITERAL_CSTRING("CREATE TABLE database" "( origin TEXT NOT NULL" ", usage INTEGER NOT NULL DEFAULT 0" ", last_vacuum_time INTEGER NOT NULL DEFAULT 0" ", last_analyze_time INTEGER NOT NULL DEFAULT 0" ", last_vacuum_size INTEGER NOT NULL DEFAULT 0" ");")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Table `data` rv = aConnection->ExecuteSimpleSQL( NS_LITERAL_CSTRING("CREATE TABLE data" "( key TEXT PRIMARY KEY" ", value TEXT NOT NULL" ", compressed INTEGER NOT NULL DEFAULT 0" ", lastAccessTime INTEGER NOT NULL DEFAULT 0" ");")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom1_0To2_0(mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "ALTER TABLE database ADD COLUMN usage INTEGER NOT NULL DEFAULT 0;")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "UPDATE database " "SET usage = (SELECT total(utf16Length(key) + utf16Length(value)) " "FROM data);")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult SetDefaultPragmas(mozIStorageConnection* aConnection) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConnection); nsresult rv = aConnection->ExecuteSimpleSQL( NS_LITERAL_CSTRING("PRAGMA synchronous = FULL;")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } #ifndef LS_MOBILE if (kSQLiteGrowthIncrement) { // This is just an optimization so ignore the failure if the disk is // currently too full. rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement, EmptyCString()); if (rv != NS_ERROR_FILE_TOO_BIG && NS_WARN_IF(NS_FAILED(rv))) { return rv; } } #endif // LS_MOBILE return NS_OK; } nsresult CreateStorageConnection(nsIFile* aDBFile, nsIFile* aUsageFile, const nsACString& aOrigin, mozIStorageConnection** aConnection, bool* aRemovedUsageFile) { AssertIsOnIOThread(); MOZ_ASSERT(aDBFile); MOZ_ASSERT(aUsageFile); MOZ_ASSERT(aConnection); MOZ_ASSERT(aRemovedUsageFile); // aRemovedUsageFile has to be initialized even when this method fails. *aRemovedUsageFile = false; nsresult rv; nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; rv = ss->OpenDatabase(aDBFile, getter_AddRefs(connection)); if (rv == NS_ERROR_FILE_CORRUPTED) { // Remove the usage file first. rv = aUsageFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Let the caller know that the usage file has been removed. *aRemovedUsageFile = true; // Nuke the database file. rv = aDBFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = ss->OpenDatabase(aDBFile, getter_AddRefs(connection)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = SetDefaultPragmas(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Check to make sure that the database schema is correct. int32_t schemaVersion; rv = connection->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (schemaVersion > kSQLiteSchemaVersion) { LS_WARNING("Unable to open LocalStorage database, schema is too high!"); return NS_ERROR_FAILURE; } if (schemaVersion != kSQLiteSchemaVersion) { const bool newDatabase = !schemaVersion; if (newDatabase) { // Set the page size first. if (kSQLitePageSizeOverride) { rv = connection->ExecuteSimpleSQL(nsPrintfCString( "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // We have to set the auto_vacuum mode before opening a transaction. rv = connection->ExecuteSimpleSQL( #ifdef LS_MOBILE // Turn on full auto_vacuum mode to reclaim disk space on mobile // devices (at the cost of some COMMIT speed). NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;") #else // Turn on incremental auto_vacuum mode on desktop builds. NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;") #endif ); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mozStorageTransaction transaction( connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); if (newDatabase) { rv = CreateTables(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion))); MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); nsCOMPtr stmt; nsresult rv = connection->CreateStatement( NS_LITERAL_CSTRING("INSERT INTO database (origin) " "VALUES (:origin)"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { // This logic needs to change next time we change the schema! static_assert(kSQLiteSchemaVersion == int32_t((2 << 4) + 0), "Upgrade function needed due to schema version increase."); while (schemaVersion != kSQLiteSchemaVersion) { if (schemaVersion == MakeSchemaVersion(1, 0)) { rv = UpgradeSchemaFrom1_0To2_0(connection); } else { LS_WARNING( "Unable to open LocalStorage database, no upgrade path is " "available!"); return NS_ERROR_FAILURE; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = connection->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); } rv = transaction.Commit(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (newDatabase) { // Windows caches the file size, let's force it to stat the file again. bool dummy; rv = aDBFile->Exists(&dummy); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } int64_t fileSize; rv = aDBFile->GetFileSize(&fileSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(fileSize > 0); PRTime vacuumTime = PR_Now(); MOZ_ASSERT(vacuumTime); nsCOMPtr vacuumTimeStmt; rv = connection->CreateStatement( NS_LITERAL_CSTRING("UPDATE database " "SET last_vacuum_time = :time" ", last_vacuum_size = :size;"), getter_AddRefs(vacuumTimeStmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("time"), vacuumTime); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("size"), fileSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = vacuumTimeStmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } connection.forget(aConnection); return NS_OK; } nsresult GetStorageConnection(const nsAString& aDatabaseFilePath, mozIStorageConnection** aConnection) { AssertIsOnConnectionThread(); MOZ_ASSERT(!aDatabaseFilePath.IsEmpty()); MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite"))); MOZ_ASSERT(aConnection); nsCOMPtr databaseFile; nsresult rv = NS_NewLocalFile(aDatabaseFilePath, false, getter_AddRefs(databaseFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = databaseFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!exists)) { return NS_ERROR_FAILURE; } nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; rv = ss->OpenDatabase(databaseFile, getter_AddRefs(connection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = SetDefaultPragmas(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } connection.forget(aConnection); return NS_OK; } nsresult GetArchiveFile(const nsAString& aStoragePath, nsIFile** aArchiveFile) { AssertIsOnIOThread(); MOZ_ASSERT(!aStoragePath.IsEmpty()); MOZ_ASSERT(aArchiveFile); nsCOMPtr archiveFile; nsresult rv = NS_NewLocalFile(aStoragePath, false, getter_AddRefs(archiveFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = archiveFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } archiveFile.forget(aArchiveFile); return NS_OK; } nsresult CreateArchiveStorageConnection(const nsAString& aStoragePath, mozIStorageConnection** aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(!aStoragePath.IsEmpty()); MOZ_ASSERT(aConnection); nsCOMPtr archiveFile; nsresult rv = GetArchiveFile(aStoragePath, getter_AddRefs(archiveFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // QuotaManager ensures this file always exists. DebugOnly exists; MOZ_ASSERT(NS_SUCCEEDED(archiveFile->Exists(&exists))); MOZ_ASSERT(exists); bool isDirectory; rv = archiveFile->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (isDirectory) { LS_WARNING("ls-archive is not a file!"); *aConnection = nullptr; return NS_OK; } nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; rv = ss->OpenUnsharedDatabase(archiveFile, getter_AddRefs(connection)); if (rv == NS_ERROR_FILE_CORRUPTED) { // Don't throw an error, leave a corrupted ls-archive database as it is. *aConnection = nullptr; return NS_OK; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = StorageDBUpdater::Update(connection); if (NS_FAILED(rv)) { // Don't throw an error, leave a non-updateable ls-archive database as // it is. *aConnection = nullptr; return NS_OK; } connection.forget(aConnection); return NS_OK; } nsresult AttachArchiveDatabase(const nsAString& aStoragePath, mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(!aStoragePath.IsEmpty()); MOZ_ASSERT(aConnection); nsCOMPtr archiveFile; nsresult rv = GetArchiveFile(aStoragePath, getter_AddRefs(archiveFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } #ifdef DEBUG bool exists; rv = archiveFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(exists); #endif nsString path; rv = archiveFile->GetPath(path); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr stmt; rv = aConnection->CreateStatement( NS_LITERAL_CSTRING("ATTACH DATABASE :path AS archive;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("path"), path); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult DetachArchiveDatabase(mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); nsresult rv = aConnection->ExecuteSimpleSQL( NS_LITERAL_CSTRING("DETACH DATABASE archive")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult GetShadowFile(const nsAString& aBasePath, nsIFile** aArchiveFile) { MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread()); MOZ_ASSERT(!aBasePath.IsEmpty()); MOZ_ASSERT(aArchiveFile); nsCOMPtr archiveFile; nsresult rv = NS_NewLocalFile(aBasePath, false, getter_AddRefs(archiveFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = archiveFile->Append(NS_LITERAL_STRING(WEB_APPS_STORE_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } archiveFile.forget(aArchiveFile); return NS_OK; } nsresult SetShadowJournalMode(mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); // Try enabling WAL mode. This can fail in various circumstances so we have to // check the results here. NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = "); NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal"); nsCOMPtr stmt; nsresult rv = aConnection->CreateStatement( journalModeQueryStart + journalModeWAL, getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasResult; rv = stmt->ExecuteStep(&hasResult); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(hasResult); nsCString journalMode; rv = stmt->GetUTF8String(0, journalMode); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (journalMode.Equals(journalModeWAL)) { // WAL mode successfully enabled. Set limits on its size here. // Set the threshold for auto-checkpointing the WAL. We don't want giant // logs slowing down us. rv = aConnection->CreateStatement(NS_LITERAL_CSTRING("PRAGMA page_size;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasResult; rv = stmt->ExecuteStep(&hasResult); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(hasResult); int32_t pageSize; rv = stmt->GetInt32(0, &pageSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536); nsAutoCString pageCount; pageCount.AppendInt(static_cast(kShadowMaxWALSize / pageSize)); rv = aConnection->ExecuteSimpleSQL( NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = ") + pageCount); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Set the maximum WAL log size to reduce footprint on mobile (large empty // WAL files will be truncated) nsAutoCString sizeLimit; sizeLimit.AppendInt(kShadowJournalSizeLimit); rv = aConnection->ExecuteSimpleSQL( NS_LITERAL_CSTRING("PRAGMA journal_size_limit = ") + sizeLimit); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { rv = aConnection->ExecuteSimpleSQL(journalModeQueryStart + NS_LITERAL_CSTRING("truncate")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } nsresult CreateShadowStorageConnection(const nsAString& aBasePath, mozIStorageConnection** aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(!aBasePath.IsEmpty()); MOZ_ASSERT(aConnection); nsCOMPtr shadowFile; nsresult rv = GetShadowFile(aBasePath, getter_AddRefs(shadowFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; rv = ss->OpenUnsharedDatabase(shadowFile, getter_AddRefs(connection)); if (rv == NS_ERROR_FILE_CORRUPTED) { rv = shadowFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = ss->OpenUnsharedDatabase(shadowFile, getter_AddRefs(connection)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = SetShadowJournalMode(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = StorageDBUpdater::Update(connection); if (NS_FAILED(rv)) { rv = connection->Close(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = shadowFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = ss->OpenUnsharedDatabase(shadowFile, getter_AddRefs(connection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = SetShadowJournalMode(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = StorageDBUpdater::Update(connection); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } connection.forget(aConnection); return NS_OK; } nsresult GetShadowStorageConnection(const nsAString& aBasePath, mozIStorageConnection** aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(!aBasePath.IsEmpty()); MOZ_ASSERT(aConnection); nsCOMPtr shadowFile; nsresult rv = GetShadowFile(aBasePath, getter_AddRefs(shadowFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = shadowFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!exists)) { return NS_ERROR_FAILURE; } nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; rv = ss->OpenUnsharedDatabase(shadowFile, getter_AddRefs(connection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } connection.forget(aConnection); return NS_OK; } nsresult AttachShadowDatabase(const nsAString& aBasePath, mozIStorageConnection* aConnection) { AssertIsOnConnectionThread(); MOZ_ASSERT(!aBasePath.IsEmpty()); MOZ_ASSERT(aConnection); nsCOMPtr shadowFile; nsresult rv = GetShadowFile(aBasePath, getter_AddRefs(shadowFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } #ifdef DEBUG bool exists; rv = shadowFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(exists); #endif nsString path; rv = shadowFile->GetPath(path); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr stmt; rv = aConnection->CreateStatement( NS_LITERAL_CSTRING("ATTACH DATABASE :path AS shadow;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("path"), path); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult DetachShadowDatabase(mozIStorageConnection* aConnection) { AssertIsOnConnectionThread(); MOZ_ASSERT(aConnection); nsresult rv = aConnection->ExecuteSimpleSQL( NS_LITERAL_CSTRING("DETACH DATABASE shadow")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult GetUsageFile(const nsAString& aDirectoryPath, nsIFile** aUsageFile) { MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread()); MOZ_ASSERT(!aDirectoryPath.IsEmpty()); MOZ_ASSERT(aUsageFile); nsCOMPtr usageFile; nsresult rv = NS_NewLocalFile(aDirectoryPath, false, getter_AddRefs(usageFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = usageFile->Append(NS_LITERAL_STRING(USAGE_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } usageFile.forget(aUsageFile); return NS_OK; } nsresult GetUsageJournalFile(const nsAString& aDirectoryPath, nsIFile** aUsageJournalFile) { MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread()); MOZ_ASSERT(!aDirectoryPath.IsEmpty()); MOZ_ASSERT(aUsageJournalFile); nsCOMPtr usageJournalFile; nsresult rv = NS_NewLocalFile(aDirectoryPath, false, getter_AddRefs(usageJournalFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = usageJournalFile->Append(NS_LITERAL_STRING(USAGE_JOURNAL_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } usageJournalFile.forget(aUsageJournalFile); return NS_OK; } nsresult UpdateUsageFile(nsIFile* aUsageFile, nsIFile* aUsageJournalFile, int64_t aUsage) { MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread()); MOZ_ASSERT(aUsageFile); MOZ_ASSERT(aUsageJournalFile); MOZ_ASSERT(aUsage >= 0); nsresult rv = aUsageJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr stream; rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aUsageFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr binaryStream = NS_NewObjectOutputStream(stream); rv = binaryStream->Write32(kUsageFileCookie); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = binaryStream->Write64(aUsage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->Close(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult LoadUsageFile(nsIFile* aUsageFile, int64_t* aUsage) { AssertIsOnIOThread(); MOZ_ASSERT(aUsageFile); MOZ_ASSERT(aUsage); int64_t fileSize; nsresult rv = aUsageFile->GetFileSize(&fileSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(fileSize != kUsageFileSize)) { return NS_ERROR_FILE_CORRUPTED; } nsCOMPtr stream; rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aUsageFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr bufferedStream; rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream.forget(), 16); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr binaryStream = NS_NewObjectInputStream(bufferedStream); uint32_t cookie; rv = binaryStream->Read32(&cookie); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(cookie != kUsageFileCookie)) { return NS_ERROR_FILE_CORRUPTED; } uint64_t usage; rv = binaryStream->Read64(&usage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aUsage = usage; return NS_OK; } /******************************************************************************* * Non-actor class declarations ******************************************************************************/ /** * Coalescing manipulation queue used by `Connection` and `DataStore`. Used by * `Connection` to buffer and coalesce manipulations applied to the Datastore * in batches by Snapshot Checkpointing until flushed to disk. Used by * `Datastore` to update `DataStore::mOrderedItems` efficiently/for code * simplification. (DataStore does not actually depend on the coalescing, as * mutations are applied atomically when a Snapshot Checkpoints, and with * `Datastore::mValues` being updated at the same time the mutations are applied * to Datastore's mWriteOptimizer.) */ class WriteOptimizer final { class WriteInfo; class AddItemInfo; class UpdateItemInfo; class RemoveItemInfo; class ClearInfo; nsAutoPtr mClearInfo; nsClassHashtable mWriteInfos; int64_t mTotalDelta; public: WriteOptimizer() : mTotalDelta(0) {} WriteOptimizer(WriteOptimizer&& aWriteOptimizer) : mClearInfo(std::move(aWriteOptimizer.mClearInfo)) { AssertIsOnBackgroundThread(); MOZ_ASSERT(&aWriteOptimizer != this); mWriteInfos.SwapElements(aWriteOptimizer.mWriteInfos); mTotalDelta = aWriteOptimizer.mTotalDelta; aWriteOptimizer.mTotalDelta = 0; } void AddItem(const nsString& aKey, const nsString& aValue, int64_t aDelta = 0); void UpdateItem(const nsString& aKey, const nsString& aValue, int64_t aDelta = 0); void RemoveItem(const nsString& aKey, int64_t aDelta = 0); void Clear(int64_t aDelta = 0); bool HasWrites() const { AssertIsOnBackgroundThread(); return mClearInfo || !mWriteInfos.IsEmpty(); } void ApplyWrites(nsTArray& aOrderedItems); nsresult PerformWrites(Connection* aConnection, bool aShadowWrites, int64_t& aOutUsage); }; /** * Base class for specific mutations. Each subclass knows how to `Perform` the * manipulation against a `Connection` and the "shadow" database (legacy * webappsstore.sqlite database that exists so LSNG can be disabled/safely * downgraded from.) */ class WriteOptimizer::WriteInfo { public: enum Type { AddItem = 0, UpdateItem, RemoveItem, Clear }; virtual Type GetType() = 0; virtual nsresult Perform(Connection* aConnection, bool aShadowWrites) = 0; virtual ~WriteInfo() = default; }; /** * SetItem mutation where the key did not previously exist. */ class WriteOptimizer::AddItemInfo : public WriteInfo { nsString mKey; nsString mValue; public: AddItemInfo(const nsAString& aKey, const nsAString& aValue) : mKey(aKey), mValue(aValue) {} const nsAString& GetKey() const { return mKey; } const nsAString& GetValue() const { return mValue; } private: Type GetType() override { return AddItem; } nsresult Perform(Connection* aConnection, bool aShadowWrites) override; }; /** * SetItem mutation where the key already existed. */ class WriteOptimizer::UpdateItemInfo final : public AddItemInfo { public: UpdateItemInfo(const nsAString& aKey, const nsAString& aValue) : AddItemInfo(aKey, aValue) {} private: Type GetType() override { return UpdateItem; } }; class WriteOptimizer::RemoveItemInfo final : public WriteInfo { nsString mKey; public: explicit RemoveItemInfo(const nsAString& aKey) : mKey(aKey) {} const nsAString& GetKey() const { return mKey; } private: Type GetType() override { return RemoveItem; } nsresult Perform(Connection* aConnection, bool aShadowWrites) override; }; /** * Clear mutation. */ class WriteOptimizer::ClearInfo final : public WriteInfo { public: ClearInfo() {} private: Type GetType() override { return Clear; } nsresult Perform(Connection* aConnection, bool aShadowWrites) override; }; class DatastoreOperationBase : public Runnable { nsCOMPtr mOwningEventTarget; nsresult mResultCode; Atomic mMayProceedOnNonOwningThread; bool mMayProceed; public: nsIEventTarget* OwningEventTarget() const { MOZ_ASSERT(mOwningEventTarget); return mOwningEventTarget; } bool IsOnOwningThread() const { MOZ_ASSERT(mOwningEventTarget); bool current; return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) && current; } void AssertIsOnOwningThread() const { MOZ_ASSERT(IsOnBackgroundThread()); MOZ_ASSERT(IsOnOwningThread()); } nsresult ResultCode() const { return mResultCode; } void SetFailureCode(nsresult aErrorCode) { MOZ_ASSERT(NS_SUCCEEDED(mResultCode)); MOZ_ASSERT(NS_FAILED(aErrorCode)); mResultCode = aErrorCode; } void MaybeSetFailureCode(nsresult aErrorCode) { MOZ_ASSERT(NS_FAILED(aErrorCode)); if (NS_SUCCEEDED(mResultCode)) { mResultCode = aErrorCode; } } void NoteComplete() { AssertIsOnOwningThread(); mMayProceed = false; mMayProceedOnNonOwningThread = false; } bool MayProceed() const { AssertIsOnOwningThread(); return mMayProceed; } // May be called on any thread, but you should call MayProceed() if you know // you're on the background thread because it is slightly faster. bool MayProceedOnNonOwningThread() const { return mMayProceedOnNonOwningThread; } protected: DatastoreOperationBase() : Runnable("dom::DatastoreOperationBase"), mOwningEventTarget(GetCurrentThreadEventTarget()), mResultCode(NS_OK), mMayProceedOnNonOwningThread(true), mMayProceed(true) {} ~DatastoreOperationBase() override { MOZ_ASSERT(!mMayProceed); } }; class ConnectionDatastoreOperationBase : public DatastoreOperationBase { protected: RefPtr mConnection; public: // This callback will be called on the background thread before releasing the // final reference to this request object. Subclasses may perform any // additional cleanup here but must always call the base class implementation. virtual void Cleanup(); protected: ConnectionDatastoreOperationBase(Connection* aConnection); ~ConnectionDatastoreOperationBase(); // Must be overridden in subclasses. Called on the target thread to allow the // subclass to perform necessary datastore operations. A successful return // value will trigger an OnSuccess callback on the background thread while // while a failure value will trigger an OnFailure callback. virtual nsresult DoDatastoreWork() = 0; // Methods that subclasses may implement. virtual void OnSuccess(); virtual void OnFailure(nsresult aResultCode); private: void RunOnConnectionThread(); void RunOnOwningThread(); // Not to be overridden by subclasses. NS_DECL_NSIRUNNABLE }; class Connection final { friend class ConnectionThread; public: class CachedStatement; private: class FlushOp; class CloseOp; RefPtr mConnectionThread; nsCOMPtr mFlushTimer; nsCOMPtr mStorageConnection; nsAutoPtr mArchivedOriginScope; nsInterfaceHashtable mCachedStatements; WriteOptimizer mWriteOptimizer; const nsCString mOrigin; const nsString mDirectoryPath; bool mFlushScheduled; #ifdef DEBUG bool mInUpdateBatch; #endif public: NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection) void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); } ArchivedOriginScope* GetArchivedOriginScope() const { return mArchivedOriginScope; } const nsCString& Origin() const { return mOrigin; } const nsString& DirectoryPath() const { return mDirectoryPath; } ////////////////////////////////////////////////////////////////////////////// // Methods which can only be called on the owning thread. // This method is used to asynchronously execute a connection datastore // operation on the connection thread. void Dispatch(ConnectionDatastoreOperationBase* aOp); // This method is used to asynchronously close the storage connection on the // connection thread. void Close(nsIRunnable* aCallback); void AddItem(const nsString& aKey, const nsString& aValue, int64_t aDelta); void UpdateItem(const nsString& aKey, const nsString& aValue, int64_t aDelta); void RemoveItem(const nsString& aKey, int64_t aDelta); void Clear(int64_t aDelta); void BeginUpdateBatch(); void EndUpdateBatch(); ////////////////////////////////////////////////////////////////////////////// // Methods which can only be called on the connection thread. nsresult EnsureStorageConnection(); mozIStorageConnection* StorageConnection() const { AssertIsOnConnectionThread(); MOZ_ASSERT(mStorageConnection); return mStorageConnection; } void CloseStorageConnection(); nsresult GetCachedStatement(const nsACString& aQuery, CachedStatement* aCachedStatement); private: // Only created by ConnectionThread. Connection(ConnectionThread* aConnectionThread, const nsACString& aOrigin, const nsAString& aDirectoryPath, nsAutoPtr&& aArchivedOriginScope); ~Connection(); void ScheduleFlush(); void Flush(); static void FlushTimerCallback(nsITimer* aTimer, void* aClosure); }; class Connection::CachedStatement final { friend class Connection; nsCOMPtr mStatement; Maybe mScoper; public: CachedStatement(); ~CachedStatement(); operator mozIStorageStatement*() const; mozIStorageStatement* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN; private: // Only called by Connection. void Assign(Connection* aConnection, already_AddRefed aStatement); // No funny business allowed. CachedStatement(const CachedStatement&) = delete; CachedStatement& operator=(const CachedStatement&) = delete; }; class Connection::FlushOp final : public ConnectionDatastoreOperationBase { RefPtr mQuotaClient; WriteOptimizer mWriteOptimizer; bool mShadowWrites; public: FlushOp(Connection* aConnection, WriteOptimizer&& aWriteOptimizer); private: nsresult DoDatastoreWork() override; }; class Connection::CloseOp final : public ConnectionDatastoreOperationBase { nsCOMPtr mCallback; public: CloseOp(Connection* aConnection, nsIRunnable* aCallback) : ConnectionDatastoreOperationBase(aConnection), mCallback(aCallback) {} private: nsresult DoDatastoreWork() override; void Cleanup() override; }; class ConnectionThread final { friend class Connection; nsCOMPtr mThread; nsRefPtrHashtable mConnections; public: ConnectionThread(); void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(ConnectionThread); } bool IsOnConnectionThread(); void AssertIsOnConnectionThread(); already_AddRefed CreateConnection( const nsACString& aOrigin, const nsAString& aDirectoryPath, nsAutoPtr&& aArchivedOriginScope); void Shutdown(); NS_INLINE_DECL_REFCOUNTING(ConnectionThread) private: ~ConnectionThread(); }; /** * Canonical state of Storage for an origin, containing all keys and their * values in the parent process. Specifically, this is the state that will * be handed out to freshly created Snapshots and that will be persisted to disk * when the Connection's flush completes. State is mutated in batches as * Snapshot instances Checkpoint their mutations locally accumulated in the * child LSSnapshots. */ class Datastore final { RefPtr mDirectoryLock; RefPtr mConnection; RefPtr mQuotaObject; nsCOMPtr mCompleteCallback; /** * PrepareDatastoreOps register themselves with the Datastore at * and unregister in PrepareDatastoreOp::Cleanup. */ nsTHashtable> mPrepareDatastoreOps; /** * PreparedDatastore instances register themselves with their associated * Datastore at construction time and unregister at destruction time. They * hang around for kPreparedDatastoreTimeoutMs in order to keep the Datastore * from closing itself via MaybeClose(), thereby giving the document enough * time to load and access LocalStorage. */ nsTHashtable> mPreparedDatastores; /** * A database is live (and in this hashtable) if it has a live LSDatabase * actor. There is at most one Database per origin per content process. Each * Database corresponds to an LSDatabase in its associated content process. */ nsTHashtable> mDatabases; /** * A database is active if it has a non-null `mSnapshot`. As long as there * are any active databases final deltas can't be calculated and * `UpdateUsage()` can't be invoked. */ nsTHashtable> mActiveDatabases; /** * Non-authoritative hashtable representation of mOrderedItems for efficient * lookup. */ nsDataHashtable mValues; /** * The authoritative ordered state of the Datastore; mValue also exists as an * unordered hashtable for efficient lookup. */ nsTArray mOrderedItems; nsTArray mPendingUsageDeltas; WriteOptimizer mWriteOptimizer; const nsCString mOrigin; const uint32_t mPrivateBrowsingId; int64_t mUsage; int64_t mUpdateBatchUsage; int64_t mSizeOfKeys; int64_t mSizeOfItems; bool mClosed; #ifdef DEBUG bool mInUpdateBatch; #endif public: // Created by PrepareDatastoreOp. Datastore(const nsACString& aOrigin, uint32_t aPrivateBrowsingId, int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems, already_AddRefed&& aDirectoryLock, already_AddRefed&& aConnection, already_AddRefed&& aQuotaObject, nsDataHashtable& aValues, nsTArray& aOrderedItems); const nsCString& Origin() const { return mOrigin; } uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; } bool IsPersistent() const { // Private-browsing is forbidden from touching disk, but // StorageAccess::eSessionScoped is allowed to touch disk because // QuotaManager's storage for such origins is wiped at shutdown. return mPrivateBrowsingId == 0; } void Close(); bool IsClosed() const { AssertIsOnBackgroundThread(); return mClosed; } void WaitForConnectionToComplete(nsIRunnable* aCallback); void NoteLivePrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp); void NoteFinishedPrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp); void NoteLivePreparedDatastore(PreparedDatastore* aPreparedDatastore); void NoteFinishedPreparedDatastore(PreparedDatastore* aPreparedDatastore); #ifdef DEBUG bool HasLivePreparedDatastores() const; #endif void NoteLiveDatabase(Database* aDatabase); void NoteFinishedDatabase(Database* aDatabase); #ifdef DEBUG bool HasLiveDatabases() const; #endif void NoteActiveDatabase(Database* aDatabase); void NoteInactiveDatabase(Database* aDatabase); void GetSnapshotInitInfo(nsTHashtable& aLoadedItems, nsTArray& aItemInfos, uint32_t& aTotalLength, int64_t& aInitialUsage, int64_t& aPeakUsage, LSSnapshot::LoadState& aLoadState); void GetItem(const nsString& aKey, nsString& aValue) const; void GetKeys(nsTArray& aKeys) const; ////////////////////////////////////////////////////////////////////////////// // Mutation Methods // // These are only called during Snapshot::RecvCheckpoint /** * Used by Snapshot::RecvCheckpoint to set a key/value pair as part of a an * explicit batch. */ void SetItem(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& aValue); void RemoveItem(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue); void Clear(Database* aDatabase, const nsString& aDocumentURI); void PrivateBrowsingClear(); void BeginUpdateBatch(int64_t aSnapshotInitialUsage); int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage); int64_t RequestUpdateUsage(int64_t aRequestedSize, int64_t aMinSize); NS_INLINE_DECL_REFCOUNTING(Datastore) private: // Reference counted. ~Datastore(); bool UpdateUsage(int64_t aDelta); void MaybeClose(); void ConnectionClosedCallback(); void CleanupMetadata(); void NotifySnapshots(Database* aDatabase, const nsAString& aKey, const nsAString& aOldValue, bool aAffectsOrder); void MarkSnapshotsDirty(); void NotifyObservers(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& aNewValue); }; class PreparedDatastore { RefPtr mDatastore; nsCOMPtr mTimer; const Maybe mContentParentId; // Strings share buffers if possible, so it's not a problem to duplicate the // origin here. const nsCString mOrigin; uint64_t mDatastoreId; bool mForPreload; bool mInvalidated; public: PreparedDatastore(Datastore* aDatastore, const Maybe& aContentParentId, const nsACString& aOrigin, uint64_t aDatastoreId, bool aForPreload) : mDatastore(aDatastore), mTimer(NS_NewTimer()), mContentParentId(aContentParentId), mOrigin(aOrigin), mDatastoreId(aDatastoreId), mForPreload(aForPreload), mInvalidated(false) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatastore); MOZ_ASSERT(mTimer); aDatastore->NoteLivePreparedDatastore(this); MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback( TimerCallback, this, kPreparedDatastoreTimeoutMs, nsITimer::TYPE_ONE_SHOT, "PreparedDatastore::TimerCallback")); } ~PreparedDatastore() { MOZ_ASSERT(mDatastore); MOZ_ASSERT(mTimer); mTimer->Cancel(); mDatastore->NoteFinishedPreparedDatastore(this); } Datastore* GetDatastore() const { AssertIsOnBackgroundThread(); MOZ_ASSERT(mDatastore); return mDatastore; } const Maybe& GetContentParentId() const { return mContentParentId; } const nsCString& Origin() const { return mOrigin; } void Invalidate() { AssertIsOnBackgroundThread(); mInvalidated = true; if (mForPreload) { mTimer->Cancel(); MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback( TimerCallback, this, 0, nsITimer::TYPE_ONE_SHOT, "PreparedDatastore::TimerCallback")); } } bool IsInvalidated() const { AssertIsOnBackgroundThread(); return mInvalidated; } private: void Destroy(); static void TimerCallback(nsITimer* aTimer, void* aClosure); }; /******************************************************************************* * Actor class declarations ******************************************************************************/ class Database final : public PBackgroundLSDatabaseParent { RefPtr mDatastore; Snapshot* mSnapshot; const PrincipalInfo mPrincipalInfo; const Maybe mContentParentId; // Strings share buffers if possible, so it's not a problem to duplicate the // origin here. nsCString mOrigin; uint32_t mPrivateBrowsingId; bool mAllowedToClose; bool mActorDestroyed; bool mRequestedAllowToClose; #ifdef DEBUG bool mActorWasAlive; #endif public: // Created in AllocPBackgroundLSDatabaseParent. Database(const PrincipalInfo& aPrincipalInfo, const Maybe& aContentParentId, const nsACString& aOrigin, uint32_t aPrivateBrowsingId); Datastore* GetDatastore() const { AssertIsOnBackgroundThread(); return mDatastore; } const PrincipalInfo& GetPrincipalInfo() const { return mPrincipalInfo; } bool IsOwnedByProcess(ContentParentId aContentParentId) const { return mContentParentId && mContentParentId.value() == aContentParentId; } uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; } const nsCString& Origin() const { return mOrigin; } void SetActorAlive(Datastore* aDatastore); void RegisterSnapshot(Snapshot* aSnapshot); void UnregisterSnapshot(Snapshot* aSnapshot); Snapshot* GetSnapshot() const { AssertIsOnBackgroundThread(); return mSnapshot; } void RequestAllowToClose(); NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Database) private: // Reference counted. ~Database(); void AllowToClose(); // IPDL methods are only called by IPDL. void ActorDestroy(ActorDestroyReason aWhy) override; mozilla::ipc::IPCResult RecvDeleteMe() override; mozilla::ipc::IPCResult RecvAllowToClose() override; PBackgroundLSSnapshotParent* AllocPBackgroundLSSnapshotParent( const nsString& aDocumentURI, const bool& aIncreasePeakUsage, const int64_t& aRequestedSize, const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override; mozilla::ipc::IPCResult RecvPBackgroundLSSnapshotConstructor( PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI, const bool& aIncreasePeakUsage, const int64_t& aRequestedSize, const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override; bool DeallocPBackgroundLSSnapshotParent( PBackgroundLSSnapshotParent* aActor) override; }; /** * Attempts to capture the state of the underlying Datastore at the time of its * creation so run-to-completion semantics can be honored. * * Rather than simply duplicate the contents of `DataStore::mValues` and * `Datastore::mOrderedItems` at the time of their creation, the Snapshot tracks * mutations to the Datastore as they happen, saving off the state of values as * they existed when the Snapshot was created. In other words, given an initial * Datastore state of { foo: 'bar', bar: 'baz' }, the Snapshot won't store those * values until it hears via `SaveItem` that "foo" is being over-written. At * that time, it will save off foo='bar' in mValues. * * ## Quota Allocation ## * * ## States ## * */ class Snapshot final : public PBackgroundLSSnapshotParent { /** * The Database that owns this snapshot. There is a 1:1 relationship between * snapshots and databases. */ RefPtr mDatabase; RefPtr mDatastore; /** * The set of keys for which values have been sent to the child LSSnapshot. * Cleared once all values have been sent as indicated by * mLoadedItems.Count()==mTotalLength and therefore mLoadedAllItems should be * true. No requests should be received for keys already in this set, and * this is enforced by fatal IPC error (unless fuzzing). */ nsTHashtable mLoadedItems; /** * The set of keys for which a RecvLoadItem request was received but there * was no such key, and so null was returned. The child LSSnapshot will also * cache these values, so redundant requests are also handled with fatal * process termination just like for mLoadedItems. Also cleared when * mLoadedAllItems becomes true because then the child can infer that all * other values must be null. (Note: this could also be done when * mLoadKeysReceived is true as a further optimization, but is not.) */ nsTHashtable mUnknownItems; /** * Values that have changed in mDatastore as reported by SaveItem * notifications that are not yet known to the child LSSnapshot. * * The naive way to snapshot the state of mDatastore would be to duplicate its * internal mValues at the time of our creation, but that is wasteful if few * changes are made to the Datastore's state. So we only track values that * are changed/evicted from the Datastore as they happen, as reported to us by * SaveItem notifications. */ nsDataHashtable mValues; /** * Latched state of mDatastore's keys during a SaveItem notification with * aAffectsOrder=true. The ordered keys needed to be saved off so that a * consistent ordering could be presented to the child LSSnapshot when it asks * for them via RecvLoadKeys. */ nsTArray mKeys; nsString mDocumentURI; /** * The number of key/value pairs that were present in the Datastore at the * time the snapshot was created. Once we have sent this many values to the * child LSSnapshot, we can infer that it has received all of the keys/values * and set mLoadedAllItems to true and clear mLoadedItems and mUnknownItems. * Note that knowing the keys/values is not the same as knowing their ordering * and so mKeys may be retained. */ uint32_t mTotalLength; int64_t mUsage; int64_t mPeakUsage; /** * True if SaveItem has saved mDatastore's keys into mKeys because a SaveItem * notification with aAffectsOrder=true was received. */ bool mSavedKeys; bool mActorDestroyed; bool mFinishReceived; bool mLoadedReceived; /** * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or * LoadState::AllUnorderedItems. It will be AllOrderedItems if the initial * snapshot contained all the data or if the state was AllOrderedKeys and * successive RecvLoadItem requests have resulted in the LSSnapshot being told * all of the key/value pairs. It will be AllUnorderedItems if the state was * LoadState::Partial and successive RecvLoadItem requests got all the * keys/values but the key ordering was not retrieved. */ bool mLoadedAllItems; /** * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or * AllOrderedKeys. This can occur because of the initial snapshot, or because * a RecvLoadKeys request was received. */ bool mLoadKeysReceived; bool mSentMarkDirty; public: // Created in AllocPBackgroundLSSnapshotParent. Snapshot(Database* aDatabase, const nsAString& aDocumentURI); void Init(nsTHashtable& aLoadedItems, uint32_t aTotalLength, int64_t aInitialUsage, int64_t aPeakUsage, LSSnapshot::LoadState aLoadState) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aInitialUsage >= 0); MOZ_ASSERT(aPeakUsage >= aInitialUsage); MOZ_ASSERT_IF(aLoadState == LSSnapshot::LoadState::AllOrderedItems, aLoadedItems.Count() == 0); MOZ_ASSERT(mTotalLength == 0); MOZ_ASSERT(mUsage == -1); MOZ_ASSERT(mPeakUsage == -1); mLoadedItems.SwapElements(aLoadedItems); mTotalLength = aTotalLength; mUsage = aInitialUsage; mPeakUsage = aPeakUsage; if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) { mLoadKeysReceived = true; } else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) { mLoadedReceived = true; mLoadedAllItems = true; mLoadKeysReceived = true; } } /** * Called via NotifySnapshots by Datastore whenever it is updating its * internal state so that snapshots can save off the state of a value at the * time of their creation. */ void SaveItem(const nsAString& aKey, const nsAString& aOldValue, bool aAffectsOrder); void MarkDirty(); NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Snapshot) private: // Reference counted. ~Snapshot(); void Finish(); // IPDL methods are only called by IPDL. void ActorDestroy(ActorDestroyReason aWhy) override; mozilla::ipc::IPCResult RecvDeleteMe() override; mozilla::ipc::IPCResult RecvCheckpoint( nsTArray&& aWriteInfos) override; mozilla::ipc::IPCResult RecvFinish() override; mozilla::ipc::IPCResult RecvLoaded() override; mozilla::ipc::IPCResult RecvLoadItem(const nsString& aKey, nsString* aValue) override; mozilla::ipc::IPCResult RecvLoadKeys(nsTArray* aKeys) override; mozilla::ipc::IPCResult RecvIncreasePeakUsage(const int64_t& aRequestedSize, const int64_t& aMinSize, int64_t* aSize) override; mozilla::ipc::IPCResult RecvPing() override; }; class Observer final : public PBackgroundLSObserverParent { nsCString mOrigin; bool mActorDestroyed; public: // Created in AllocPBackgroundLSObserverParent. explicit Observer(const nsACString& aOrigin); const nsCString& Origin() const { return mOrigin; } void Observe(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& aNewValue); NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Observer) private: // Reference counted. ~Observer(); // IPDL methods are only called by IPDL. void ActorDestroy(ActorDestroyReason aWhy) override; mozilla::ipc::IPCResult RecvDeleteMe() override; }; class LSRequestBase : public DatastoreOperationBase, public PBackgroundLSRequestParent { protected: enum class State { // Just created on the PBackground thread. Next step is Opening. Initial, // Waiting to open/opening on the main thread. Next step is either // Nesting if a subclass needs to process more nested states or // SendingReadyMessage if a subclass doesn't need any nested processing. Opening, // Doing nested processing. Nesting, // Waiting to send/sending the ready message on the PBackground thread. Next // step is WaitingForFinish. SendingReadyMessage, // Waiting for the finish message on the PBackground thread. Next step is // SendingResults. WaitingForFinish, // Waiting to send/sending results on the PBackground thread. Next step is // Completed. SendingResults, // All done. Completed }; nsCOMPtr mMainEventTarget; State mState; public: explicit LSRequestBase(nsIEventTarget* aMainEventTarget); void Dispatch(); protected: ~LSRequestBase() override; virtual nsresult Open() = 0; virtual nsresult NestedRun(); virtual void GetResponse(LSRequestResponse& aResponse) = 0; virtual void Cleanup() {} private: void SendReadyMessage(); void SendResults(); protected: // Common nsIRunnable implementation that subclasses may not override. NS_IMETHOD Run() final; // IPDL methods. void ActorDestroy(ActorDestroyReason aWhy) override; private: mozilla::ipc::IPCResult RecvCancel() final; mozilla::ipc::IPCResult RecvFinish() final; }; class PrepareDatastoreOp : public LSRequestBase, public OpenDirectoryListener { class LoadDataOp; enum class NestedState { // The nesting has not yet taken place. Next step is // CheckExistingOperations. BeforeNesting, // Checking if a prepare datastore operation is already running for given // origin on the PBackground thread. Next step is CheckClosingDatastore. CheckExistingOperations, // Checking if a datastore is closing the connection for given origin on // the PBackground thread. Next step is PreparationPending. CheckClosingDatastore, // Opening directory or initializing quota manager on the PBackground // thread. Next step is either DirectoryOpenPending if quota manager is // already initialized or QuotaManagerPending if quota manager needs to be // initialized. // If a datastore already exists for given origin then the next state is // SendingReadyMessage. PreparationPending, // Waiting for quota manager initialization to complete on the PBackground // thread. Next step is either SendingReadyMessage if initialization failed // or DirectoryOpenPending if initialization succeeded. QuotaManagerPending, // Waiting for directory open allowed on the PBackground thread. The next // step is either SendingReadyMessage if directory lock failed to acquire, // or DatabaseWorkOpen if directory lock is acquired. DirectoryOpenPending, // Waiting to do/doing work on the QuotaManager IO thread. Its next step is // BeginLoadData. DatabaseWorkOpen, // Starting a load data operation on the PBackground thread. Next step is // DatabaseWorkLoadData. BeginLoadData, // Waiting to do/doing work on the connection thread. This involves waiting // for the LoadDataOp to do its work. Eventually the state will transition // to SendingReadyMessage. DatabaseWorkLoadData, // The nesting has completed. AfterNesting }; nsCOMPtr mMainEventTarget; RefPtr mDelayedOp; RefPtr mDirectoryLock; RefPtr mConnection; RefPtr mDatastore; nsAutoPtr mArchivedOriginScope; LoadDataOp* mLoadDataOp; nsDataHashtable mValues; nsTArray mOrderedItems; const LSRequestPrepareDatastoreParams mParams; Maybe mContentParentId; nsCString mSuffix; nsCString mGroup; nsCString mMainThreadOrigin; nsCString mOrigin; nsString mDirectoryPath; nsString mDatabaseFilePath; uint32_t mPrivateBrowsingId; int64_t mUsage; int64_t mSizeOfKeys; int64_t mSizeOfItems; NestedState mNestedState; bool mDatabaseNotAvailable; bool mRequestedDirectoryLock; bool mInvalidated; #ifdef DEBUG int64_t mDEBUGUsage; #endif public: PrepareDatastoreOp(nsIEventTarget* aMainEventTarget, const LSRequestParams& aParams, const Maybe& aContentParentId); bool OriginIsKnown() const { AssertIsOnOwningThread(); return !mOrigin.IsEmpty(); } const nsCString& Origin() const { AssertIsOnOwningThread(); MOZ_ASSERT(OriginIsKnown()); return mOrigin; } bool RequestedDirectoryLock() const { AssertIsOnOwningThread(); return mRequestedDirectoryLock; } void Invalidate() { AssertIsOnOwningThread(); mInvalidated = true; } private: ~PrepareDatastoreOp() override; nsresult Open() override; nsresult CheckExistingOperations(); nsresult CheckClosingDatastoreInternal(); nsresult CheckClosingDatastore(); nsresult BeginDatastorePreparationInternal(); nsresult BeginDatastorePreparation(); nsresult QuotaManagerOpen(); nsresult OpenDirectory(); void SendToIOThread(); nsresult DatabaseWork(); nsresult DatabaseNotAvailable(); nsresult EnsureDirectoryEntry(nsIFile* aEntry, bool aCreateIfNotExists, bool aDirectory, bool* aAlreadyExisted = nullptr); nsresult VerifyDatabaseInformation(mozIStorageConnection* aConnection); already_AddRefed GetQuotaObject(); nsresult BeginLoadData(); void FinishNesting(); nsresult FinishNestingOnNonOwningThread(); nsresult NestedRun() override; void GetResponse(LSRequestResponse& aResponse) override; void Cleanup() override; void ConnectionClosedCallback(); void CleanupMetadata(); NS_DECL_ISUPPORTS_INHERITED // IPDL overrides. void ActorDestroy(ActorDestroyReason aWhy) override; // OpenDirectoryListener overrides. void DirectoryLockAcquired(DirectoryLock* aLock) override; void DirectoryLockFailed() override; }; class PrepareDatastoreOp::LoadDataOp final : public ConnectionDatastoreOperationBase { RefPtr mPrepareDatastoreOp; public: explicit LoadDataOp(PrepareDatastoreOp* aPrepareDatastoreOp) : ConnectionDatastoreOperationBase(aPrepareDatastoreOp->mConnection), mPrepareDatastoreOp(aPrepareDatastoreOp) {} private: ~LoadDataOp() = default; nsresult DoDatastoreWork() override; void OnSuccess() override; void OnFailure(nsresult aResultCode) override; void Cleanup() override; }; class PrepareObserverOp : public LSRequestBase { const LSRequestPrepareObserverParams mParams; nsCString mOrigin; public: PrepareObserverOp(nsIEventTarget* aMainEventTarget, const LSRequestParams& aParams); private: nsresult Open() override; void GetResponse(LSRequestResponse& aResponse) override; }; class LSSimpleRequestBase : public DatastoreOperationBase, public PBackgroundLSSimpleRequestParent { protected: enum class State { // Just created on the PBackground thread. Next step is Opening. Initial, // Waiting to open/opening on the main thread. Next step is SendingResults. Opening, // Waiting to send/sending results on the PBackground thread. Next step is // Completed. SendingResults, // All done. Completed }; State mState; public: LSSimpleRequestBase(); void Dispatch(); protected: ~LSSimpleRequestBase() override; virtual nsresult Open() = 0; virtual void GetResponse(LSSimpleRequestResponse& aResponse) = 0; private: void SendResults(); // Common nsIRunnable implementation that subclasses may not override. NS_IMETHOD Run() final; // IPDL methods. void ActorDestroy(ActorDestroyReason aWhy) override; }; class PreloadedOp : public LSSimpleRequestBase { const LSSimpleRequestPreloadedParams mParams; nsCString mOrigin; public: explicit PreloadedOp(const LSSimpleRequestParams& aParams); private: nsresult Open() override; void GetResponse(LSSimpleRequestResponse& aResponse) override; }; /******************************************************************************* * Other class declarations ******************************************************************************/ struct ArchivedOriginInfo { OriginAttributes mOriginAttributes; nsCString mOriginNoSuffix; ArchivedOriginInfo(const OriginAttributes& aOriginAttributes, const nsACString& aOriginNoSuffix) : mOriginAttributes(aOriginAttributes), mOriginNoSuffix(aOriginNoSuffix) {} }; class ArchivedOriginScope { struct Origin { nsCString mOriginSuffix; nsCString mOriginNoSuffix; Origin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) : mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix) {} const nsACString& OriginSuffix() const { return mOriginSuffix; } const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; } }; struct Prefix { nsCString mOriginNoSuffix; explicit Prefix(const nsACString& aOriginNoSuffix) : mOriginNoSuffix(aOriginNoSuffix) {} const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; } }; struct Pattern { UniquePtr mPattern; explicit Pattern(const OriginAttributesPattern& aPattern) : mPattern(MakeUnique(aPattern)) {} Pattern(const Pattern& aOther) : mPattern(MakeUnique(*aOther.mPattern)) {} Pattern(Pattern&& aOther) = default; const OriginAttributesPattern& GetPattern() const { MOZ_ASSERT(mPattern); return *mPattern; } }; struct Null {}; using DataType = Variant; DataType mData; public: static ArchivedOriginScope* CreateFromOrigin(nsIPrincipal* aPrincipal); static ArchivedOriginScope* CreateFromPrefix(nsIPrincipal* aPrincipal); static ArchivedOriginScope* CreateFromPattern( const OriginAttributesPattern& aPattern); static ArchivedOriginScope* CreateFromNull(); bool IsOrigin() const { return mData.is(); } bool IsPrefix() const { return mData.is(); } bool IsPattern() const { return mData.is(); } bool IsNull() const { return mData.is(); } const nsACString& OriginSuffix() const { MOZ_ASSERT(IsOrigin()); return mData.as().OriginSuffix(); } const nsACString& OriginNoSuffix() const { MOZ_ASSERT(IsOrigin() || IsPrefix()); if (IsOrigin()) { return mData.as().OriginNoSuffix(); } return mData.as().OriginNoSuffix(); } const OriginAttributesPattern& GetPattern() const { MOZ_ASSERT(IsPattern()); return mData.as().GetPattern(); } void GetBindingClause(nsACString& aBindingClause) const; nsresult BindToStatement(mozIStorageStatement* aStatement) const; bool HasMatches(ArchivedOriginHashtable* aHashtable) const; void RemoveMatches(ArchivedOriginHashtable* aHashtable) const; private: // Move constructors explicit ArchivedOriginScope(const Origin&& aOrigin) : mData(aOrigin) {} explicit ArchivedOriginScope(const Pattern&& aPattern) : mData(aPattern) {} explicit ArchivedOriginScope(const Prefix&& aPrefix) : mData(aPrefix) {} explicit ArchivedOriginScope(const Null&& aNull) : mData(aNull) {} }; class ArchivedOriginScopeHelper : public Runnable { Monitor mMonitor; const OriginAttributes mAttrs; const nsCString mSpec; nsAutoPtr mArchivedOriginScope; nsresult mMainThreadResultCode; bool mWaiting; bool mPrefix; public: ArchivedOriginScopeHelper(const nsACString& aSpec, const OriginAttributes& aAttrs, bool aPrefix) : Runnable("dom::localstorage::ArchivedOriginScopeHelper"), mMonitor("ArchivedOriginScopeHelper::mMonitor"), mAttrs(aAttrs), mSpec(aSpec), mMainThreadResultCode(NS_OK), mWaiting(true), mPrefix(aPrefix) { AssertIsOnIOThread(); } nsresult BlockAndReturnArchivedOriginScope( nsAutoPtr& aArchivedOriginScope); private: nsresult RunOnMainThread(); NS_DECL_NSIRUNNABLE }; class QuotaClient final : public mozilla::dom::quota::Client { class Observer; class MatchFunction; static QuotaClient* sInstance; Mutex mShadowDatabaseMutex; bool mShutdownRequested; public: QuotaClient(); static nsresult Initialize(); static QuotaClient* GetInstance() { AssertIsOnBackgroundThread(); return sInstance; } static bool IsShuttingDownOnBackgroundThread() { AssertIsOnBackgroundThread(); if (sInstance) { return sInstance->IsShuttingDown(); } return QuotaManager::IsShuttingDown(); } static bool IsShuttingDownOnNonBackgroundThread() { MOZ_ASSERT(!IsOnBackgroundThread()); return QuotaManager::IsShuttingDown(); } mozilla::Mutex& ShadowDatabaseMutex() { MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread()); return mShadowDatabaseMutex; } bool IsShuttingDown() const { AssertIsOnBackgroundThread(); return mShutdownRequested; } NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::QuotaClient, override) Type GetType() override; nsresult InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) override; nsresult GetUsageForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) override; nsresult AboutToClearOrigins( const Nullable& aPersistenceType, const OriginScope& aOriginScope) override; void OnOriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin) override; void ReleaseIOThreadObjects() override; void AbortOperations(const nsACString& aOrigin) override; void AbortOperationsForProcess(ContentParentId aContentParentId) override; void StartIdleMaintenance() override; void StopIdleMaintenance() override; void ShutdownWorkThreads() override; private: ~QuotaClient() override; nsresult CreateArchivedOriginScope( const OriginScope& aOriginScope, nsAutoPtr& aArchivedOriginScope); nsresult PerformDelete(mozIStorageConnection* aConnection, const nsACString& aSchemaName, ArchivedOriginScope* aArchivedOriginScope) const; }; class QuotaClient::Observer final : public nsIObserver { public: static nsresult Initialize(); private: Observer() { MOZ_ASSERT(NS_IsMainThread()); } ~Observer() { MOZ_ASSERT(NS_IsMainThread()); } nsresult Init(); nsresult Shutdown(); NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER }; class QuotaClient::MatchFunction final : public mozIStorageFunction { OriginAttributesPattern mPattern; public: explicit MatchFunction(const OriginAttributesPattern& aPattern) : mPattern(aPattern) {} private: ~MatchFunction() = default; NS_DECL_ISUPPORTS NS_DECL_MOZISTORAGEFUNCTION }; /******************************************************************************* * Globals ******************************************************************************/ #ifdef DEBUG bool gLocalStorageInitialized = false; #endif typedef nsTArray PrepareDatastoreOpArray; StaticAutoPtr gPrepareDatastoreOps; typedef nsDataHashtable DatastoreHashtable; StaticAutoPtr gDatastores; uint64_t gLastDatastoreId = 0; typedef nsClassHashtable PreparedDatastoreHashtable; StaticAutoPtr gPreparedDatastores; typedef nsTArray LiveDatabaseArray; StaticAutoPtr gLiveDatabases; StaticRefPtr gConnectionThread; uint64_t gLastObserverId = 0; typedef nsRefPtrHashtable PreparedObserverHashtable; StaticAutoPtr gPreparedObsevers; typedef nsClassHashtable> ObserverHashtable; StaticAutoPtr gObservers; Atomic gOriginLimitKB(kDefaultOriginLimitKB); Atomic gShadowWrites(kDefaultShadowWrites); Atomic gSnapshotPrefill(kDefaultSnapshotPrefill); typedef nsDataHashtable UsageHashtable; // Can only be touched on the Quota Manager I/O thread. StaticAutoPtr gUsages; StaticAutoPtr gArchivedOrigins; // Can only be touched on the Quota Manager I/O thread. bool gInitializedShadowStorage = false; bool IsOnConnectionThread() { MOZ_ASSERT(gConnectionThread); return gConnectionThread->IsOnConnectionThread(); } void AssertIsOnConnectionThread() { MOZ_ASSERT(gConnectionThread); gConnectionThread->AssertIsOnConnectionThread(); } void InitUsageForOrigin(const nsACString& aOrigin, int64_t aUsage) { AssertIsOnIOThread(); if (!gUsages) { gUsages = new UsageHashtable(); } MOZ_ASSERT(!gUsages->Contains(aOrigin)); gUsages->Put(aOrigin, aUsage); } void UpdateUsageForOrigin(const nsACString& aOrigin, int64_t aUsage) { AssertIsOnIOThread(); MOZ_ASSERT(gUsages); MOZ_ASSERT(gUsages->Contains(aOrigin)); gUsages->Put(aOrigin, aUsage); } nsresult LoadArchivedOrigins() { AssertIsOnIOThread(); MOZ_ASSERT(!gArchivedOrigins); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); // Ensure that the webappsstore.sqlite is moved to new place. nsresult rv = quotaManager->EnsureStorageIsInitialized(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; rv = CreateArchiveStorageConnection(quotaManager->GetStoragePath(), getter_AddRefs(connection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!connection) { gArchivedOrigins = new ArchivedOriginHashtable(); return NS_OK; } nsCOMPtr stmt; rv = connection->CreateStatement( NS_LITERAL_CSTRING("SELECT DISTINCT originAttributes, originKey " "FROM webappsstore2;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoPtr archivedOrigins( new ArchivedOriginHashtable()); bool hasResult; while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult) { nsCString originSuffix; rv = stmt->GetUTF8String(0, originSuffix); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString originNoSuffix; rv = stmt->GetUTF8String(1, originNoSuffix); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString hashKey = GetArchivedOriginHashKey(originSuffix, originNoSuffix); OriginAttributes originAttributes; if (NS_WARN_IF(!originAttributes.PopulateFromSuffix(originSuffix))) { return NS_ERROR_FAILURE; } nsAutoPtr archivedOriginInfo( new ArchivedOriginInfo(originAttributes, originNoSuffix)); archivedOrigins->Put(hashKey, archivedOriginInfo.forget()); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } gArchivedOrigins = archivedOrigins.forget(); return NS_OK; } nsresult GetUsage(mozIStorageConnection* aConnection, ArchivedOriginScope* aArchivedOriginScope, int64_t* aUsage) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); MOZ_ASSERT(aUsage); nsresult rv; nsCOMPtr stmt; if (aArchivedOriginScope) { rv = aConnection->CreateStatement( NS_LITERAL_CSTRING("SELECT " "total(utf16Length(key) + utf16Length(value)) " "FROM webappsstore2 " "WHERE originKey = :originKey " "AND originAttributes = :originAttributes;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aArchivedOriginScope->BindToStatement(stmt); } else { rv = aConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT usage " "FROM database"), getter_AddRefs(stmt)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasResult; rv = stmt->ExecuteStep(&hasResult); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!hasResult)) { return NS_ERROR_FAILURE; } int64_t usage; rv = stmt->GetInt64(0, &usage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aUsage = usage; return NS_OK; } void ShadowWritesPrefChangedCallback(const char* aPrefName, void* aClosure) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!strcmp(aPrefName, kShadowWritesPref)); MOZ_ASSERT(!aClosure); gShadowWrites = Preferences::GetBool(aPrefName, kDefaultShadowWrites); } void SnapshotPrefillPrefChangedCallback(const char* aPrefName, void* aClosure) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!strcmp(aPrefName, kSnapshotPrefillPref)); MOZ_ASSERT(!aClosure); int32_t snapshotPrefill = Preferences::GetInt(aPrefName, kDefaultSnapshotPrefill); // The magic -1 is for use only by tests. if (snapshotPrefill == -1) { snapshotPrefill = INT32_MAX; } gSnapshotPrefill = snapshotPrefill; } } // namespace /******************************************************************************* * Exported functions ******************************************************************************/ void InitializeLocalStorage() { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!gLocalStorageInitialized); if (!QuotaManager::IsRunningGTests()) { // This service has to be started on the main thread currently. nsCOMPtr ss; if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) { NS_WARNING("Failed to get storage service!"); } } if (NS_FAILED(QuotaClient::Initialize())) { NS_WARNING("Failed to initialize quota client!"); } if (NS_FAILED(Preferences::AddAtomicUintVarCache( &gOriginLimitKB, kDefaultQuotaPref, kDefaultOriginLimitKB))) { NS_WARNING("Unable to respond to default quota pref changes!"); } Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback, kShadowWritesPref); Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback, kSnapshotPrefillPref); #ifdef DEBUG gLocalStorageInitialized = true; #endif } PBackgroundLSDatabaseParent* AllocPBackgroundLSDatabaseParent( const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, const uint64_t& aDatastoreId) { AssertIsOnBackgroundThread(); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { return nullptr; } if (NS_WARN_IF(!gPreparedDatastores)) { ASSERT_UNLESS_FUZZING(); return nullptr; } PreparedDatastore* preparedDatastore = gPreparedDatastores->Get(aDatastoreId); if (NS_WARN_IF(!preparedDatastore)) { ASSERT_UNLESS_FUZZING(); return nullptr; } // If we ever decide to return null from this point on, we need to make sure // that the datastore is closed and the prepared datastore is removed from the // gPreparedDatastores hashtable. // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor // once we return a valid actor in this method. RefPtr database = new Database(aPrincipalInfo, preparedDatastore->GetContentParentId(), preparedDatastore->Origin(), aPrivateBrowsingId); // Transfer ownership to IPDL. return database.forget().take(); } bool RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor, const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, const uint64_t& aDatastoreId) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(gPreparedDatastores); MOZ_ASSERT(gPreparedDatastores->Get(aDatastoreId)); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); // The actor is now completely built (it has a manager, channel and it's // registered as a subprotocol). // ActorDestroy will be called if we fail here. nsAutoPtr preparedDatastore; gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore); MOZ_ASSERT(preparedDatastore); auto* database = static_cast(aActor); database->SetActorAlive(preparedDatastore->GetDatastore()); // It's possible that AbortOperations was called before the database actor // was created and became live. Let the child know that the database in no // longer valid. if (preparedDatastore->IsInvalidated()) { database->RequestAllowToClose(); } return true; } bool DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); // Transfer ownership back from IPDL. RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } PBackgroundLSObserverParent* AllocPBackgroundLSObserverParent( const uint64_t& aObserverId) { AssertIsOnBackgroundThread(); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { return nullptr; } if (NS_WARN_IF(!gPreparedObsevers)) { ASSERT_UNLESS_FUZZING(); return nullptr; } RefPtr observer = gPreparedObsevers->Get(aObserverId); if (NS_WARN_IF(!observer)) { ASSERT_UNLESS_FUZZING(); return nullptr; } // observer->SetObject(this); // Transfer ownership to IPDL. return observer.forget().take(); } bool RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor, const uint64_t& aObserverId) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(gPreparedObsevers); MOZ_ASSERT(gPreparedObsevers->GetWeak(aObserverId)); RefPtr observer; gPreparedObsevers->Remove(aObserverId, observer.StartAssignment()); MOZ_ASSERT(observer); if (!gPreparedObsevers->Count()) { gPreparedObsevers = nullptr; } if (!gObservers) { gObservers = new ObserverHashtable(); } nsTArray* array; if (!gObservers->Get(observer->Origin(), &array)) { array = new nsTArray(); gObservers->Put(observer->Origin(), array); } array->AppendElement(observer); return true; } bool DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); // Transfer ownership back from IPDL. RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent( PBackgroundParent* aBackgroundActor, const LSRequestParams& aParams) { AssertIsOnBackgroundThread(); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { return nullptr; } // If we're in the same process as the actor, we need to get the target event // queue from the current RequestHelper. nsCOMPtr mainEventTarget; if (!BackgroundParent::IsOtherProcessActor(aBackgroundActor)) { mainEventTarget = LSObject::GetSyncLoopEventTarget(); } RefPtr actor; switch (aParams.type()) { case LSRequestParams::TLSRequestPrepareDatastoreParams: { Maybe contentParentId; uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor); if (childID) { contentParentId = Some(ContentParentId(childID)); } RefPtr prepareDatastoreOp = new PrepareDatastoreOp(mainEventTarget, aParams, contentParentId); if (!gPrepareDatastoreOps) { gPrepareDatastoreOps = new PrepareDatastoreOpArray(); } gPrepareDatastoreOps->AppendElement(prepareDatastoreOp); actor = std::move(prepareDatastoreOp); break; } case LSRequestParams::TLSRequestPrepareObserverParams: { RefPtr prepareObserverOp = new PrepareObserverOp(mainEventTarget, aParams); actor = std::move(prepareObserverOp); break; } default: MOZ_CRASH("Should never get here!"); } // Transfer ownership to IPDL. return actor.forget().take(); } bool RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor, const LSRequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != LSRequestParams::T__None); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); // The actor is now completely built. auto* op = static_cast(aActor); op->Dispatch(); return true; } bool DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor) { AssertIsOnBackgroundThread(); // Transfer ownership back from IPDL. RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent( const LSSimpleRequestParams& aParams) { AssertIsOnBackgroundThread(); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { return nullptr; } RefPtr actor; switch (aParams.type()) { case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: { RefPtr preloadedOp = new PreloadedOp(aParams); actor = std::move(preloadedOp); break; } default: MOZ_CRASH("Should never get here!"); } // Transfer ownership to IPDL. return actor.forget().take(); } bool RecvPBackgroundLSSimpleRequestConstructor( PBackgroundLSSimpleRequestParent* aActor, const LSSimpleRequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); // The actor is now completely built. auto* op = static_cast(aActor); op->Dispatch(); return true; } bool DeallocPBackgroundLSSimpleRequestParent( PBackgroundLSSimpleRequestParent* aActor) { AssertIsOnBackgroundThread(); // Transfer ownership back from IPDL. RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } bool RecvLSClearPrivateBrowsing() { AssertIsOnBackgroundThread(); if (gDatastores) { for (auto iter = gDatastores->ConstIter(); !iter.Done(); iter.Next()) { Datastore* datastore = iter.Data(); MOZ_ASSERT(datastore); if (datastore->PrivateBrowsingId()) { datastore->PrivateBrowsingClear(); } } } return true; } namespace localstorage { already_AddRefed CreateQuotaClient() { AssertIsOnBackgroundThread(); MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); RefPtr client = new QuotaClient(); return client.forget(); } } // namespace localstorage /******************************************************************************* * WriteOptimizer ******************************************************************************/ void WriteOptimizer::AddItem(const nsString& aKey, const nsString& aValue, int64_t aDelta) { AssertIsOnBackgroundThread(); WriteInfo* existingWriteInfo; nsAutoPtr newWriteInfo; if (mWriteInfos.Get(aKey, &existingWriteInfo) && existingWriteInfo->GetType() == WriteInfo::RemoveItem) { newWriteInfo = new UpdateItemInfo(aKey, aValue); } else { newWriteInfo = new AddItemInfo(aKey, aValue); } mWriteInfos.Put(aKey, newWriteInfo.forget()); mTotalDelta += aDelta; } void WriteOptimizer::UpdateItem(const nsString& aKey, const nsString& aValue, int64_t aDelta) { AssertIsOnBackgroundThread(); WriteInfo* existingWriteInfo; nsAutoPtr newWriteInfo; if (mWriteInfos.Get(aKey, &existingWriteInfo) && existingWriteInfo->GetType() == WriteInfo::AddItem) { newWriteInfo = new AddItemInfo(aKey, aValue); } else { newWriteInfo = new UpdateItemInfo(aKey, aValue); } mWriteInfos.Put(aKey, newWriteInfo.forget()); mTotalDelta += aDelta; } void WriteOptimizer::RemoveItem(const nsString& aKey, int64_t aDelta) { AssertIsOnBackgroundThread(); WriteInfo* existingWriteInfo; if (mWriteInfos.Get(aKey, &existingWriteInfo) && existingWriteInfo->GetType() == WriteInfo::AddItem) { mWriteInfos.Remove(aKey); } else { nsAutoPtr newWriteInfo(new RemoveItemInfo(aKey)); mWriteInfos.Put(aKey, newWriteInfo.forget()); } mTotalDelta += aDelta; } void WriteOptimizer::Clear(int64_t aDelta) { AssertIsOnBackgroundThread(); mWriteInfos.Clear(); if (!mClearInfo) { mClearInfo = new ClearInfo(); } mTotalDelta += aDelta; } void WriteOptimizer::ApplyWrites(nsTArray& aOrderedItems) { AssertIsOnBackgroundThread(); if (mClearInfo) { aOrderedItems.Clear(); mClearInfo = nullptr; } for (int32_t index = aOrderedItems.Length() - 1; index >= 0; index--) { LSItemInfo& item = aOrderedItems[index]; if (auto entry = mWriteInfos.Lookup(item.key())) { WriteInfo* writeInfo = entry.Data(); switch (writeInfo->GetType()) { case WriteInfo::RemoveItem: aOrderedItems.RemoveElementAt(index); entry.Remove(); break; case WriteInfo::UpdateItem: { auto updateItemInfo = static_cast(writeInfo); item.value() = updateItemInfo->GetValue(); entry.Remove(); break; } case WriteInfo::AddItem: break; default: MOZ_CRASH("Bad type!"); } } } for (auto iter = mWriteInfos.ConstIter(); !iter.Done(); iter.Next()) { WriteInfo* writeInfo = iter.Data(); MOZ_ASSERT(writeInfo->GetType() == WriteInfo::AddItem); auto addItemInfo = static_cast(writeInfo); LSItemInfo* itemInfo = aOrderedItems.AppendElement(); itemInfo->key() = addItemInfo->GetKey(); itemInfo->value() = addItemInfo->GetValue(); } mWriteInfos.Clear(); } nsresult WriteOptimizer::PerformWrites(Connection* aConnection, bool aShadowWrites, int64_t& aOutUsage) { AssertIsOnConnectionThread(); MOZ_ASSERT(aConnection); nsresult rv; if (mClearInfo) { rv = mClearInfo->Perform(aConnection, aShadowWrites); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } for (auto iter = mWriteInfos.ConstIter(); !iter.Done(); iter.Next()) { rv = iter.Data()->Perform(aConnection, aShadowWrites); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } Connection::CachedStatement stmt; rv = aConnection->GetCachedStatement( NS_LITERAL_CSTRING("UPDATE database " "SET usage = usage + :delta"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), mTotalDelta); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING("SELECT usage " "FROM database"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasResult; rv = stmt->ExecuteStep(&hasResult); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!hasResult)) { return NS_ERROR_FAILURE; } int64_t usage; rv = stmt->GetInt64(0, &usage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aOutUsage = usage; return NS_OK; } nsresult WriteOptimizer::AddItemInfo::Perform(Connection* aConnection, bool aShadowWrites) { AssertIsOnConnectionThread(); MOZ_ASSERT(aConnection); Connection::CachedStatement stmt; nsresult rv = aConnection->GetCachedStatement( NS_LITERAL_CSTRING("INSERT OR REPLACE INTO data (key, value) " "VALUES(:key, :value)"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!aShadowWrites) { return NS_OK; } rv = aConnection->GetCachedStatement( NS_LITERAL_CSTRING( "INSERT OR REPLACE INTO shadow.webappsstore2 " "(originAttributes, originKey, scope, key, value) " "VALUES (:originAttributes, :originKey, :scope, :key, :value) "), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } ArchivedOriginScope* archivedOriginScope = aConnection->GetArchivedOriginScope(); rv = archivedOriginScope->BindToStatement(stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString scope = Scheme0Scope(archivedOriginScope->OriginSuffix(), archivedOriginScope->OriginNoSuffix()); rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), scope); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult WriteOptimizer::RemoveItemInfo::Perform(Connection* aConnection, bool aShadowWrites) { AssertIsOnConnectionThread(); MOZ_ASSERT(aConnection); Connection::CachedStatement stmt; nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING("DELETE FROM data " "WHERE key = :key;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!aShadowWrites) { return NS_OK; } rv = aConnection->GetCachedStatement( NS_LITERAL_CSTRING("DELETE FROM shadow.webappsstore2 " "WHERE originAttributes = :originAttributes " "AND originKey = :originKey " "AND key = :key;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection->GetArchivedOriginScope()->BindToStatement(stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult WriteOptimizer::ClearInfo::Perform(Connection* aConnection, bool aShadowWrites) { AssertIsOnConnectionThread(); MOZ_ASSERT(aConnection); Connection::CachedStatement stmt; nsresult rv = aConnection->GetCachedStatement( NS_LITERAL_CSTRING("DELETE FROM data;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!aShadowWrites) { return NS_OK; } rv = aConnection->GetCachedStatement( NS_LITERAL_CSTRING("DELETE FROM shadow.webappsstore2 " "WHERE originAttributes = :originAttributes " "AND originKey = :originKey;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection->GetArchivedOriginScope()->BindToStatement(stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } /******************************************************************************* * DatastoreOperationBase ******************************************************************************/ /******************************************************************************* * ConnectionDatastoreOperationBase ******************************************************************************/ ConnectionDatastoreOperationBase::ConnectionDatastoreOperationBase( Connection* aConnection) : mConnection(aConnection) { MOZ_ASSERT(aConnection); } ConnectionDatastoreOperationBase::~ConnectionDatastoreOperationBase() { MOZ_ASSERT(!mConnection, "ConnectionDatabaseOperationBase::Cleanup() was not called by a " "subclass!"); } void ConnectionDatastoreOperationBase::Cleanup() { AssertIsOnOwningThread(); MOZ_ASSERT(mConnection); mConnection = nullptr; NoteComplete(); } void ConnectionDatastoreOperationBase::OnSuccess() { AssertIsOnOwningThread(); } void ConnectionDatastoreOperationBase::OnFailure(nsresult aResultCode) { AssertIsOnOwningThread(); MOZ_ASSERT(NS_FAILED(aResultCode)); } void ConnectionDatastoreOperationBase::RunOnConnectionThread() { AssertIsOnConnectionThread(); MOZ_ASSERT(mConnection); MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); if (!MayProceedOnNonOwningThread()) { SetFailureCode(NS_ERROR_FAILURE); } else { nsresult rv = mConnection->EnsureStorageConnection(); if (NS_WARN_IF(NS_FAILED(rv))) { SetFailureCode(rv); } else { MOZ_ASSERT(mConnection->StorageConnection()); rv = DoDatastoreWork(); if (NS_FAILED(rv)) { SetFailureCode(rv); } } } MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); } void ConnectionDatastoreOperationBase::RunOnOwningThread() { AssertIsOnOwningThread(); MOZ_ASSERT(mConnection); if (!MayProceed()) { MaybeSetFailureCode(NS_ERROR_FAILURE); } else { if (NS_SUCCEEDED(ResultCode())) { OnSuccess(); } else { OnFailure(ResultCode()); } } Cleanup(); } NS_IMETHODIMP ConnectionDatastoreOperationBase::Run() { if (IsOnConnectionThread()) { RunOnConnectionThread(); } else { RunOnOwningThread(); } return NS_OK; } /******************************************************************************* * Connection implementation ******************************************************************************/ Connection::Connection(ConnectionThread* aConnectionThread, const nsACString& aOrigin, const nsAString& aDirectoryPath, nsAutoPtr&& aArchivedOriginScope) : mConnectionThread(aConnectionThread), mArchivedOriginScope(std::move(aArchivedOriginScope)), mOrigin(aOrigin), mDirectoryPath(aDirectoryPath), mFlushScheduled(false) #ifdef DEBUG , mInUpdateBatch(false) #endif { AssertIsOnOwningThread(); MOZ_ASSERT(!aOrigin.IsEmpty()); MOZ_ASSERT(!aDirectoryPath.IsEmpty()); } Connection::~Connection() { AssertIsOnOwningThread(); MOZ_ASSERT(!mStorageConnection); MOZ_ASSERT(!mCachedStatements.Count()); MOZ_ASSERT(!mInUpdateBatch); MOZ_ASSERT(!mFlushScheduled); } void Connection::Dispatch(ConnectionDatastoreOperationBase* aOp) { AssertIsOnOwningThread(); MOZ_ASSERT(mConnectionThread); MOZ_ALWAYS_SUCCEEDS( mConnectionThread->mThread->Dispatch(aOp, NS_DISPATCH_NORMAL)); } void Connection::Close(nsIRunnable* aCallback) { AssertIsOnOwningThread(); MOZ_ASSERT(aCallback); if (mFlushScheduled) { MOZ_ASSERT(mFlushTimer); MOZ_ALWAYS_SUCCEEDS(mFlushTimer->Cancel()); Flush(); mFlushTimer = nullptr; } RefPtr op = new CloseOp(this, aCallback); Dispatch(op); } void Connection::AddItem(const nsString& aKey, const nsString& aValue, int64_t aDelta) { AssertIsOnOwningThread(); MOZ_ASSERT(mInUpdateBatch); mWriteOptimizer.AddItem(aKey, aValue, aDelta); } void Connection::UpdateItem(const nsString& aKey, const nsString& aValue, int64_t aDelta) { AssertIsOnOwningThread(); MOZ_ASSERT(mInUpdateBatch); mWriteOptimizer.UpdateItem(aKey, aValue, aDelta); } void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) { AssertIsOnOwningThread(); MOZ_ASSERT(mInUpdateBatch); mWriteOptimizer.RemoveItem(aKey, aDelta); } void Connection::Clear(int64_t aDelta) { AssertIsOnOwningThread(); MOZ_ASSERT(mInUpdateBatch); mWriteOptimizer.Clear(aDelta); } void Connection::BeginUpdateBatch() { AssertIsOnOwningThread(); MOZ_ASSERT(!mInUpdateBatch); #ifdef DEBUG mInUpdateBatch = true; #endif } void Connection::EndUpdateBatch() { AssertIsOnOwningThread(); MOZ_ASSERT(mInUpdateBatch); if (mWriteOptimizer.HasWrites() && !mFlushScheduled) { ScheduleFlush(); } #ifdef DEBUG mInUpdateBatch = false; #endif } nsresult Connection::EnsureStorageConnection() { AssertIsOnConnectionThread(); if (!mStorageConnection) { nsCOMPtr file; nsresult rv = NS_NewLocalFile(mDirectoryPath, false, getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = file->Append(NS_LITERAL_STRING(DATA_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsString filePath; rv = file->GetPath(filePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr storageConnection; rv = GetStorageConnection(filePath, getter_AddRefs(storageConnection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mStorageConnection = storageConnection; } return NS_OK; } void Connection::CloseStorageConnection() { AssertIsOnConnectionThread(); MOZ_ASSERT(mStorageConnection); mCachedStatements.Clear(); MOZ_ALWAYS_SUCCEEDS(mStorageConnection->Close()); mStorageConnection = nullptr; } nsresult Connection::GetCachedStatement(const nsACString& aQuery, CachedStatement* aCachedStatement) { AssertIsOnConnectionThread(); MOZ_ASSERT(!aQuery.IsEmpty()); MOZ_ASSERT(aCachedStatement); MOZ_ASSERT(mStorageConnection); nsCOMPtr stmt; if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) { nsresult rv = mStorageConnection->CreateStatement(aQuery, getter_AddRefs(stmt)); if (NS_FAILED(rv)) { #ifdef DEBUG nsCString msg; MOZ_ALWAYS_SUCCEEDS(mStorageConnection->GetLastErrorString(msg)); nsAutoCString error = NS_LITERAL_CSTRING("The statement '") + aQuery + NS_LITERAL_CSTRING("' failed to compile with the error message '") + msg + NS_LITERAL_CSTRING("'."); NS_WARNING(error.get()); #endif return rv; } mCachedStatements.Put(aQuery, stmt); } aCachedStatement->Assign(this, stmt.forget()); return NS_OK; } void Connection::ScheduleFlush() { AssertIsOnOwningThread(); MOZ_ASSERT(mWriteOptimizer.HasWrites()); MOZ_ASSERT(!mFlushScheduled); if (!mFlushTimer) { mFlushTimer = NS_NewTimer(); MOZ_ASSERT(mFlushTimer); } MOZ_ALWAYS_SUCCEEDS(mFlushTimer->InitWithNamedFuncCallback( FlushTimerCallback, this, kFlushTimeoutMs, nsITimer::TYPE_ONE_SHOT, "Connection::FlushTimerCallback")); mFlushScheduled = true; } void Connection::Flush() { AssertIsOnOwningThread(); MOZ_ASSERT(mFlushScheduled); if (mWriteOptimizer.HasWrites()) { RefPtr op = new FlushOp(this, std::move(mWriteOptimizer)); Dispatch(op); } mFlushScheduled = false; } // static void Connection::FlushTimerCallback(nsITimer* aTimer, void* aClosure) { MOZ_ASSERT(aClosure); auto* self = static_cast(aClosure); MOZ_ASSERT(self); MOZ_ASSERT(self->mFlushScheduled); self->Flush(); } Connection::CachedStatement::CachedStatement() { AssertIsOnConnectionThread(); MOZ_COUNT_CTOR(Connection::CachedStatement); } Connection::CachedStatement::~CachedStatement() { AssertIsOnConnectionThread(); MOZ_COUNT_DTOR(Connection::CachedStatement); } Connection::CachedStatement::operator mozIStorageStatement*() const { AssertIsOnConnectionThread(); return mStatement; } mozIStorageStatement* Connection::CachedStatement::operator->() const { AssertIsOnConnectionThread(); MOZ_ASSERT(mStatement); return mStatement; } void Connection::CachedStatement::Assign( Connection* aConnection, already_AddRefed aStatement) { AssertIsOnConnectionThread(); mScoper.reset(); mStatement = aStatement; if (mStatement) { mScoper.emplace(mStatement); } } Connection::FlushOp::FlushOp(Connection* aConnection, WriteOptimizer&& aWriteOptimizer) : ConnectionDatastoreOperationBase(aConnection), mQuotaClient(QuotaClient::GetInstance()), mWriteOptimizer(std::move(aWriteOptimizer)), mShadowWrites(gShadowWrites) { MOZ_ASSERT(mQuotaClient); } nsresult Connection::FlushOp::DoDatastoreWork() { AssertIsOnConnectionThread(); MOZ_ASSERT(mConnection); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); nsCOMPtr storageConnection = mConnection->StorageConnection(); MOZ_ASSERT(storageConnection); nsresult rv; Maybe shadowDatabaseLock; if (mShadowWrites) { MOZ_ASSERT(mQuotaClient); shadowDatabaseLock.emplace(mQuotaClient->ShadowDatabaseMutex()); rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } CachedStatement stmt; rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } int64_t usage; rv = mWriteOptimizer.PerformWrites(mConnection, mShadowWrites, usage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr usageFile; rv = GetUsageFile(mConnection->DirectoryPath(), getter_AddRefs(usageFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr usageJournalFile; rv = GetUsageJournalFile(mConnection->DirectoryPath(), getter_AddRefs(usageJournalFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = UpdateUsageFile(usageFile, usageJournalFile, usage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (mShadowWrites) { rv = DetachShadowDatabase(storageConnection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } shadowDatabaseLock.reset(); } rv = usageJournalFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } RefPtr runnable = NS_NewRunnableFunction("dom::localstorage::UpdateUsageRunnable", [origin = mConnection->Origin(), usage]() { UpdateUsageForOrigin(origin, usage); }); MOZ_ALWAYS_SUCCEEDS( quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL)); return NS_OK; } nsresult Connection::CloseOp::DoDatastoreWork() { AssertIsOnConnectionThread(); MOZ_ASSERT(mConnection); mConnection->CloseStorageConnection(); return NS_OK; } void Connection::CloseOp::Cleanup() { AssertIsOnOwningThread(); MOZ_ASSERT(mConnection); mConnection->mConnectionThread->mConnections.Remove(mConnection->mOrigin); nsCOMPtr callback; mCallback.swap(callback); callback->Run(); ConnectionDatastoreOperationBase::Cleanup(); } /******************************************************************************* * ConnectionThread implementation ******************************************************************************/ ConnectionThread::ConnectionThread() { AssertIsOnOwningThread(); AssertIsOnBackgroundThread(); MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread("LS Thread", getter_AddRefs(mThread))); } ConnectionThread::~ConnectionThread() { AssertIsOnOwningThread(); MOZ_ASSERT(!mConnections.Count()); } bool ConnectionThread::IsOnConnectionThread() { MOZ_ASSERT(mThread); bool current; return NS_SUCCEEDED(mThread->IsOnCurrentThread(¤t)) && current; } void ConnectionThread::AssertIsOnConnectionThread() { MOZ_ASSERT(IsOnConnectionThread()); } already_AddRefed ConnectionThread::CreateConnection( const nsACString& aOrigin, const nsAString& aDirectoryPath, nsAutoPtr&& aArchivedOriginScope) { AssertIsOnOwningThread(); MOZ_ASSERT(!aOrigin.IsEmpty()); MOZ_ASSERT(!mConnections.GetWeak(aOrigin)); RefPtr connection = new Connection( this, aOrigin, aDirectoryPath, std::move(aArchivedOriginScope)); mConnections.Put(aOrigin, connection); return connection.forget(); } void ConnectionThread::Shutdown() { AssertIsOnOwningThread(); MOZ_ASSERT(mThread); mThread->Shutdown(); } /******************************************************************************* * Datastore ******************************************************************************/ Datastore::Datastore(const nsACString& aOrigin, uint32_t aPrivateBrowsingId, int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems, already_AddRefed&& aDirectoryLock, already_AddRefed&& aConnection, already_AddRefed&& aQuotaObject, nsDataHashtable& aValues, nsTArray& aOrderedItems) : mDirectoryLock(std::move(aDirectoryLock)), mConnection(std::move(aConnection)), mQuotaObject(std::move(aQuotaObject)), mOrigin(aOrigin), mPrivateBrowsingId(aPrivateBrowsingId), mUsage(aUsage), mUpdateBatchUsage(-1), mSizeOfKeys(aSizeOfKeys), mSizeOfItems(aSizeOfItems), mClosed(false) #ifdef DEBUG , mInUpdateBatch(false) #endif { AssertIsOnBackgroundThread(); mValues.SwapElements(aValues); mOrderedItems.SwapElements(aOrderedItems); } Datastore::~Datastore() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mClosed); } void Datastore::Close() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mClosed); MOZ_ASSERT(!mDatabases.Count()); MOZ_ASSERT(mDirectoryLock); mClosed = true; if (IsPersistent()) { MOZ_ASSERT(mConnection); MOZ_ASSERT(mQuotaObject); // We can't release the directory lock and unregister itself from the // hashtable until the connection is fully closed. nsCOMPtr callback = NewRunnableMethod("dom::Datastore::ConnectionClosedCallback", this, &Datastore::ConnectionClosedCallback); mConnection->Close(callback); } else { MOZ_ASSERT(!mConnection); MOZ_ASSERT(!mQuotaObject); // There's no connection, so it's safe to release the directory lock and // unregister itself from the hashtable. mDirectoryLock = nullptr; CleanupMetadata(); } } void Datastore::WaitForConnectionToComplete(nsIRunnable* aCallback) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aCallback); MOZ_ASSERT(!mCompleteCallback); MOZ_ASSERT(mClosed); mCompleteCallback = aCallback; } void Datastore::NoteLivePrepareDatastoreOp( PrepareDatastoreOp* aPrepareDatastoreOp) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aPrepareDatastoreOp); MOZ_ASSERT(!mPrepareDatastoreOps.GetEntry(aPrepareDatastoreOp)); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(!mClosed); mPrepareDatastoreOps.PutEntry(aPrepareDatastoreOp); } void Datastore::NoteFinishedPrepareDatastoreOp( PrepareDatastoreOp* aPrepareDatastoreOp) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aPrepareDatastoreOp); MOZ_ASSERT(mPrepareDatastoreOps.GetEntry(aPrepareDatastoreOp)); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(!mClosed); mPrepareDatastoreOps.RemoveEntry(aPrepareDatastoreOp); MaybeClose(); } void Datastore::NoteLivePreparedDatastore( PreparedDatastore* aPreparedDatastore) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aPreparedDatastore); MOZ_ASSERT(!mPreparedDatastores.GetEntry(aPreparedDatastore)); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(!mClosed); mPreparedDatastores.PutEntry(aPreparedDatastore); } void Datastore::NoteFinishedPreparedDatastore( PreparedDatastore* aPreparedDatastore) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aPreparedDatastore); MOZ_ASSERT(mPreparedDatastores.GetEntry(aPreparedDatastore)); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(!mClosed); mPreparedDatastores.RemoveEntry(aPreparedDatastore); MaybeClose(); } #ifdef DEBUG bool Datastore::HasLivePreparedDatastores() const { AssertIsOnBackgroundThread(); return mPreparedDatastores.Count(); } #endif void Datastore::NoteLiveDatabase(Database* aDatabase) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); MOZ_ASSERT(!mDatabases.GetEntry(aDatabase)); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(!mClosed); mDatabases.PutEntry(aDatabase); } void Datastore::NoteFinishedDatabase(Database* aDatabase) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); MOZ_ASSERT(mDatabases.GetEntry(aDatabase)); MOZ_ASSERT(!mActiveDatabases.GetEntry(aDatabase)); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(!mClosed); mDatabases.RemoveEntry(aDatabase); MaybeClose(); } #ifdef DEBUG bool Datastore::HasLiveDatabases() const { AssertIsOnBackgroundThread(); return mDatabases.Count(); } #endif void Datastore::NoteActiveDatabase(Database* aDatabase) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); MOZ_ASSERT(mDatabases.GetEntry(aDatabase)); MOZ_ASSERT(!mActiveDatabases.GetEntry(aDatabase)); MOZ_ASSERT(!mClosed); mActiveDatabases.PutEntry(aDatabase); } void Datastore::NoteInactiveDatabase(Database* aDatabase) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); MOZ_ASSERT(mDatabases.GetEntry(aDatabase)); MOZ_ASSERT(mActiveDatabases.GetEntry(aDatabase)); MOZ_ASSERT(!mClosed); mActiveDatabases.RemoveEntry(aDatabase); if (!mActiveDatabases.Count() && mPendingUsageDeltas.Length()) { int64_t finalDelta = 0; for (auto delta : mPendingUsageDeltas) { finalDelta += delta; } MOZ_ASSERT(finalDelta <= 0); if (finalDelta != 0) { DebugOnly ok = UpdateUsage(finalDelta); MOZ_ASSERT(ok); } mPendingUsageDeltas.Clear(); } } void Datastore::GetSnapshotInitInfo(nsTHashtable& aLoadedItems, nsTArray& aItemInfos, uint32_t& aTotalLength, int64_t& aInitialUsage, int64_t& aPeakUsage, LSSnapshot::LoadState& aLoadState) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mClosed); MOZ_ASSERT(!mInUpdateBatch); #ifdef DEBUG int64_t sizeOfKeys = 0; int64_t sizeOfItems = 0; for (auto item : mOrderedItems) { int64_t sizeOfKey = static_cast(item.key().Length()); sizeOfKeys += sizeOfKey; sizeOfItems += sizeOfKey + static_cast(item.value().Length()); } MOZ_ASSERT(mSizeOfKeys == sizeOfKeys); MOZ_ASSERT(mSizeOfItems == sizeOfItems); #endif int64_t size = 0; if (mSizeOfKeys <= gSnapshotPrefill) { if (mSizeOfItems <= gSnapshotPrefill) { aItemInfos.AppendElements(mOrderedItems); aLoadState = LSSnapshot::LoadState::AllOrderedItems; } else { nsString value; for (auto item : mOrderedItems) { if (!value.IsVoid()) { value = item.value(); size += static_cast(item.key().Length()) + static_cast(value.Length()); if (size <= gSnapshotPrefill) { aLoadedItems.PutEntry(item.key()); } else { value.SetIsVoid(true); } } LSItemInfo* itemInfo = aItemInfos.AppendElement(); itemInfo->key() = item.key(); itemInfo->value() = value; } aLoadState = LSSnapshot::LoadState::AllOrderedKeys; } } else { for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) { const nsAString& key = iter.Key(); const nsString& value = iter.Data(); size += static_cast(key.Length()) + static_cast(value.Length()); if (size > gSnapshotPrefill) { break; } aLoadedItems.PutEntry(key); LSItemInfo* itemInfo = aItemInfos.AppendElement(); itemInfo->key() = iter.Key(); itemInfo->value() = iter.Data(); } MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length()); aLoadState = LSSnapshot::LoadState::Partial; } aTotalLength = mValues.Count(); aInitialUsage = mUsage; aPeakUsage = aInitialUsage; } void Datastore::GetItem(const nsString& aKey, nsString& aValue) const { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mClosed); if (!mValues.Get(aKey, &aValue)) { aValue.SetIsVoid(true); } } void Datastore::GetKeys(nsTArray& aKeys) const { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mClosed); for (auto item : mOrderedItems) { aKeys.AppendElement(item.key()); } } void Datastore::SetItem(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& aValue) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); MOZ_ASSERT(!mClosed); MOZ_ASSERT(mInUpdateBatch); nsString oldValue; GetItem(aKey, oldValue); if (oldValue != aValue || oldValue.IsVoid() != aValue.IsVoid()) { bool isNewItem = oldValue.IsVoid(); NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem); mValues.Put(aKey, aValue); int64_t sizeOfItem; if (isNewItem) { mWriteOptimizer.AddItem(aKey, aValue); int64_t sizeOfKey = static_cast(aKey.Length()); sizeOfItem = sizeOfKey + static_cast(aValue.Length()); mUpdateBatchUsage += sizeOfItem; mSizeOfKeys += sizeOfKey; mSizeOfItems += sizeOfItem; } else { mWriteOptimizer.UpdateItem(aKey, aValue); sizeOfItem = static_cast(aValue.Length()) - static_cast(oldValue.Length()); mUpdateBatchUsage += sizeOfItem; mSizeOfItems += sizeOfItem; } if (IsPersistent()) { if (oldValue.IsVoid()) { mConnection->AddItem(aKey, aValue, sizeOfItem); } else { mConnection->UpdateItem(aKey, aValue, sizeOfItem); } } } NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, aValue); } void Datastore::RemoveItem(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); MOZ_ASSERT(!mClosed); MOZ_ASSERT(mInUpdateBatch); nsString oldValue; GetItem(aKey, oldValue); if (!oldValue.IsVoid()) { NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true); mValues.Remove(aKey); mWriteOptimizer.RemoveItem(aKey); int64_t sizeOfKey = static_cast(aKey.Length()); int64_t sizeOfItem = sizeOfKey + static_cast(oldValue.Length()); mUpdateBatchUsage -= sizeOfItem; mSizeOfKeys -= sizeOfKey; mSizeOfItems -= sizeOfItem; if (IsPersistent()) { mConnection->RemoveItem(aKey, -sizeOfItem); } } NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidString()); } void Datastore::Clear(Database* aDatabase, const nsString& aDocumentURI) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); MOZ_ASSERT(!mClosed); MOZ_ASSERT(mInUpdateBatch); if (mValues.Count()) { int64_t sizeOfItems = 0; for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) { const nsAString& key = iter.Key(); const nsAString& value = iter.Data(); sizeOfItems += (static_cast(key.Length()) + static_cast(value.Length())); NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true); } mValues.Clear(); mWriteOptimizer.Clear(); mUpdateBatchUsage -= sizeOfItems; mSizeOfKeys = 0; mSizeOfItems = 0; if (IsPersistent()) { mConnection->Clear(-sizeOfItems); } } NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidString(), VoidString()); } void Datastore::PrivateBrowsingClear() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mPrivateBrowsingId); MOZ_ASSERT(!mClosed); MOZ_ASSERT(!mInUpdateBatch); if (mValues.Count()) { MarkSnapshotsDirty(); mValues.Clear(); mOrderedItems.Clear(); DebugOnly ok = UpdateUsage(-mSizeOfItems); MOZ_ASSERT(ok); mSizeOfKeys = 0; mSizeOfItems = 0; } } void Datastore::BeginUpdateBatch(int64_t aSnapshotInitialUsage) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aSnapshotInitialUsage >= 0); MOZ_ASSERT(!mClosed); MOZ_ASSERT(mUpdateBatchUsage == -1); MOZ_ASSERT(!mInUpdateBatch); mUpdateBatchUsage = aSnapshotInitialUsage; if (IsPersistent()) { mConnection->BeginUpdateBatch(); } #ifdef DEBUG mInUpdateBatch = true; #endif } int64_t Datastore::EndUpdateBatch(int64_t aSnapshotPeakUsage) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mClosed); MOZ_ASSERT(mInUpdateBatch); mWriteOptimizer.ApplyWrites(mOrderedItems); if (aSnapshotPeakUsage >= 0) { int64_t delta = mUpdateBatchUsage - aSnapshotPeakUsage; if (mActiveDatabases.Count()) { // We can't apply deltas while other databases are still active. // The final delta must be zero or negative, but individual deltas can // be positive. A positive delta can't be applied asynchronously since // there's no way to fire the quota exceeded error event. mPendingUsageDeltas.AppendElement(delta); } else { MOZ_ASSERT(delta <= 0); if (delta != 0) { DebugOnly ok = UpdateUsage(delta); MOZ_ASSERT(ok); } } } int64_t result = mUpdateBatchUsage; mUpdateBatchUsage = -1; if (IsPersistent()) { mConnection->EndUpdateBatch(); } #ifdef DEBUG mInUpdateBatch = false; #endif return result; } int64_t Datastore::RequestUpdateUsage(int64_t aRequestedSize, int64_t aMinSize) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aRequestedSize > 0); MOZ_ASSERT(aMinSize > 0); if (UpdateUsage(aRequestedSize)) { return aRequestedSize; } if (UpdateUsage(aMinSize)) { return aMinSize; } return 0; } bool Datastore::UpdateUsage(int64_t aDelta) { AssertIsOnBackgroundThread(); // Check internal LocalStorage origin limit. int64_t newUsage = mUsage + aDelta; MOZ_ASSERT(newUsage >= 0); if (newUsage > gOriginLimitKB * 1024) { return false; } // Check QuotaManager limits (group and global limit). if (IsPersistent()) { MOZ_ASSERT(mQuotaObject); if (!mQuotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) { return false; } } // Quota checks passed, set new usage. mUsage = newUsage; return true; } void Datastore::MaybeClose() { AssertIsOnBackgroundThread(); if (!mPrepareDatastoreOps.Count() && !mPreparedDatastores.Count() && !mDatabases.Count()) { Close(); } } void Datastore::ConnectionClosedCallback() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(mConnection); MOZ_ASSERT(mQuotaObject); MOZ_ASSERT(mClosed); // Release the quota object first. mQuotaObject = nullptr; // Now it's safe to release the directory lock and unregister itself from // the hashtable. mDirectoryLock = nullptr; mConnection = nullptr; CleanupMetadata(); if (mCompleteCallback) { MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget())); } } void Datastore::CleanupMetadata() { AssertIsOnBackgroundThread(); MOZ_ASSERT(gDatastores); MOZ_ASSERT(gDatastores->Get(mOrigin)); gDatastores->Remove(mOrigin); if (!gDatastores->Count()) { gDatastores = nullptr; } } void Datastore::NotifySnapshots(Database* aDatabase, const nsAString& aKey, const nsAString& aOldValue, bool aAffectsOrder) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); for (auto iter = mDatabases.ConstIter(); !iter.Done(); iter.Next()) { Database* database = iter.Get()->GetKey(); if (database == aDatabase) { continue; } Snapshot* snapshot = database->GetSnapshot(); if (snapshot) { snapshot->SaveItem(aKey, aOldValue, aAffectsOrder); } } } void Datastore::MarkSnapshotsDirty() { AssertIsOnBackgroundThread(); for (auto iter = mDatabases.ConstIter(); !iter.Done(); iter.Next()) { Database* database = iter.Get()->GetKey(); Snapshot* snapshot = database->GetSnapshot(); if (snapshot) { snapshot->MarkDirty(); } } } void Datastore::NotifyObservers(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& aNewValue) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); if (!gObservers) { return; } nsTArray* array; if (!gObservers->Get(mOrigin, &array)) { return; } MOZ_ASSERT(array); // We do not want to send information about events back to the content process // that caused the change. PBackgroundParent* databaseBackgroundActor = aDatabase->Manager(); for (Observer* observer : *array) { if (observer->Manager() != databaseBackgroundActor) { observer->Observe(aDatabase, aDocumentURI, aKey, aOldValue, aNewValue); } } } /******************************************************************************* * PreparedDatastore ******************************************************************************/ void PreparedDatastore::Destroy() { AssertIsOnBackgroundThread(); MOZ_ASSERT(gPreparedDatastores); MOZ_ASSERT(gPreparedDatastores->Get(mDatastoreId)); nsAutoPtr preparedDatastore; gPreparedDatastores->Remove(mDatastoreId, &preparedDatastore); MOZ_ASSERT(preparedDatastore); } // static void PreparedDatastore::TimerCallback(nsITimer* aTimer, void* aClosure) { AssertIsOnBackgroundThread(); auto* self = static_cast(aClosure); MOZ_ASSERT(self); self->Destroy(); } /******************************************************************************* * Database ******************************************************************************/ Database::Database(const PrincipalInfo& aPrincipalInfo, const Maybe& aContentParentId, const nsACString& aOrigin, uint32_t aPrivateBrowsingId) : mSnapshot(nullptr), mPrincipalInfo(aPrincipalInfo), mContentParentId(aContentParentId), mOrigin(aOrigin), mPrivateBrowsingId(aPrivateBrowsingId), mAllowedToClose(false), mActorDestroyed(false), mRequestedAllowToClose(false) #ifdef DEBUG , mActorWasAlive(false) #endif { AssertIsOnBackgroundThread(); } Database::~Database() { MOZ_ASSERT_IF(mActorWasAlive, mAllowedToClose); MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed); } void Database::SetActorAlive(Datastore* aDatastore) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mActorWasAlive); MOZ_ASSERT(!mActorDestroyed); #ifdef DEBUG mActorWasAlive = true; #endif mDatastore = aDatastore; mDatastore->NoteLiveDatabase(this); if (!gLiveDatabases) { gLiveDatabases = new LiveDatabaseArray(); } gLiveDatabases->AppendElement(this); } void Database::RegisterSnapshot(Snapshot* aSnapshot) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aSnapshot); MOZ_ASSERT(!mSnapshot); MOZ_ASSERT(!mAllowedToClose); // Only one snapshot at a time is currently supported. mSnapshot = aSnapshot; mDatastore->NoteActiveDatabase(this); } void Database::UnregisterSnapshot(Snapshot* aSnapshot) { MOZ_ASSERT(aSnapshot); MOZ_ASSERT(mSnapshot == aSnapshot); mSnapshot = nullptr; mDatastore->NoteInactiveDatabase(this); } void Database::RequestAllowToClose() { AssertIsOnBackgroundThread(); if (mRequestedAllowToClose) { return; } mRequestedAllowToClose = true; // Send the RequestAllowToClose message to the child to avoid racing with the // child actor. Except the case when the actor was already destroyed. if (mActorDestroyed) { MOZ_ASSERT(mAllowedToClose); } else { Unused << SendRequestAllowToClose(); } } void Database::AllowToClose() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mAllowedToClose); MOZ_ASSERT(mDatastore); mAllowedToClose = true; mDatastore->NoteFinishedDatabase(this); mDatastore = nullptr; MOZ_ASSERT(gLiveDatabases); gLiveDatabases->RemoveElement(this); if (gLiveDatabases->IsEmpty()) { gLiveDatabases = nullptr; } } void Database::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mActorDestroyed); mActorDestroyed = true; if (!mAllowedToClose) { AllowToClose(); } } mozilla::ipc::IPCResult Database::RecvDeleteMe() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mActorDestroyed); IProtocol* mgr = Manager(); if (!PBackgroundLSDatabaseParent::Send__delete__(this)) { return IPC_FAIL_NO_REASON(mgr); } return IPC_OK(); } mozilla::ipc::IPCResult Database::RecvAllowToClose() { AssertIsOnBackgroundThread(); if (NS_WARN_IF(mAllowedToClose)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } AllowToClose(); return IPC_OK(); } PBackgroundLSSnapshotParent* Database::AllocPBackgroundLSSnapshotParent( const nsString& aDocumentURI, const bool& aIncreasePeakUsage, const int64_t& aRequestedSize, const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) { AssertIsOnBackgroundThread(); if (NS_WARN_IF(aIncreasePeakUsage && aRequestedSize <= 0)) { ASSERT_UNLESS_FUZZING(); return nullptr; } if (NS_WARN_IF(aIncreasePeakUsage && aMinSize <= 0)) { ASSERT_UNLESS_FUZZING(); return nullptr; } if (NS_WARN_IF(mAllowedToClose)) { ASSERT_UNLESS_FUZZING(); return nullptr; } RefPtr snapshot = new Snapshot(this, aDocumentURI); // Transfer ownership to IPDL. return snapshot.forget().take(); } mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor( PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI, const bool& aIncreasePeakUsage, const int64_t& aRequestedSize, const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) { AssertIsOnBackgroundThread(); MOZ_ASSERT_IF(aIncreasePeakUsage, aRequestedSize > 0); MOZ_ASSERT_IF(aIncreasePeakUsage, aMinSize > 0); MOZ_ASSERT(aInitInfo); MOZ_ASSERT(!mAllowedToClose); auto* snapshot = static_cast(aActor); // TODO: This can be optimized depending on which operation triggers snapshot // creation. For example clear() doesn't need to receive items at all. nsTHashtable loadedItems; nsTArray itemInfos; uint32_t totalLength; int64_t initialUsage; int64_t peakUsage; LSSnapshot::LoadState loadState; mDatastore->GetSnapshotInitInfo(loadedItems, itemInfos, totalLength, initialUsage, peakUsage, loadState); if (aIncreasePeakUsage) { int64_t size = mDatastore->RequestUpdateUsage(aRequestedSize, aMinSize); peakUsage += size; } snapshot->Init(loadedItems, totalLength, initialUsage, peakUsage, loadState); RegisterSnapshot(snapshot); aInitInfo->itemInfos() = std::move(itemInfos); aInitInfo->totalLength() = totalLength; aInitInfo->initialUsage() = initialUsage; aInitInfo->peakUsage() = peakUsage; aInitInfo->loadState() = loadState; return IPC_OK(); } bool Database::DeallocPBackgroundLSSnapshotParent( PBackgroundLSSnapshotParent* aActor) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); // Transfer ownership back from IPDL. RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } /******************************************************************************* * Snapshot ******************************************************************************/ Snapshot::Snapshot(Database* aDatabase, const nsAString& aDocumentURI) : mDatabase(aDatabase), mDatastore(aDatabase->GetDatastore()), mDocumentURI(aDocumentURI), mTotalLength(0), mUsage(-1), mPeakUsage(-1), mSavedKeys(false), mActorDestroyed(false), mFinishReceived(false), mLoadedReceived(false), mLoadedAllItems(false), mLoadKeysReceived(false), mSentMarkDirty(false) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); } Snapshot::~Snapshot() { MOZ_ASSERT(mActorDestroyed); MOZ_ASSERT(mFinishReceived); } void Snapshot::SaveItem(const nsAString& aKey, const nsAString& aOldValue, bool aAffectsOrder) { AssertIsOnBackgroundThread(); MarkDirty(); if (mLoadedAllItems) { return; } if (!mLoadedItems.GetEntry(aKey) && !mUnknownItems.GetEntry(aKey)) { nsString oldValue(aOldValue); mValues.LookupForAdd(aKey).OrInsert([oldValue]() { return oldValue; }); } if (aAffectsOrder && !mSavedKeys && !mLoadKeysReceived) { mDatastore->GetKeys(mKeys); mSavedKeys = true; } } void Snapshot::MarkDirty() { AssertIsOnBackgroundThread(); if (!mSentMarkDirty) { Unused << SendMarkDirty(); mSentMarkDirty = true; } } void Snapshot::Finish() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mDatabase); MOZ_ASSERT(mDatastore); MOZ_ASSERT(!mFinishReceived); mDatastore->BeginUpdateBatch(mUsage); mDatastore->EndUpdateBatch(mPeakUsage); mDatabase->UnregisterSnapshot(this); mFinishReceived = true; } void Snapshot::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mActorDestroyed); mActorDestroyed = true; if (!mFinishReceived) { Finish(); } } mozilla::ipc::IPCResult Snapshot::RecvDeleteMe() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mActorDestroyed); IProtocol* mgr = Manager(); if (!PBackgroundLSSnapshotParent::Send__delete__(this)) { return IPC_FAIL_NO_REASON(mgr); } return IPC_OK(); } mozilla::ipc::IPCResult Snapshot::RecvCheckpoint( nsTArray&& aWriteInfos) { AssertIsOnBackgroundThread(); MOZ_ASSERT(mUsage >= 0); MOZ_ASSERT(mPeakUsage >= mUsage); if (NS_WARN_IF(aWriteInfos.IsEmpty())) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } mDatastore->BeginUpdateBatch(mUsage); for (uint32_t index = 0; index < aWriteInfos.Length(); index++) { const LSWriteInfo& writeInfo = aWriteInfos[index]; switch (writeInfo.type()) { case LSWriteInfo::TLSSetItemInfo: { const LSSetItemInfo& info = writeInfo.get_LSSetItemInfo(); mDatastore->SetItem(mDatabase, mDocumentURI, info.key(), info.oldValue(), info.value()); break; } case LSWriteInfo::TLSRemoveItemInfo: { const LSRemoveItemInfo& info = writeInfo.get_LSRemoveItemInfo(); mDatastore->RemoveItem(mDatabase, mDocumentURI, info.key(), info.oldValue()); break; } case LSWriteInfo::TLSClearInfo: { mDatastore->Clear(mDatabase, mDocumentURI); break; } default: MOZ_CRASH("Should never get here!"); } } mUsage = mDatastore->EndUpdateBatch(-1); return IPC_OK(); } mozilla::ipc::IPCResult Snapshot::RecvFinish() { AssertIsOnBackgroundThread(); if (NS_WARN_IF(mFinishReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } Finish(); return IPC_OK(); } mozilla::ipc::IPCResult Snapshot::RecvLoaded() { AssertIsOnBackgroundThread(); if (NS_WARN_IF(mFinishReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mLoadedReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mLoadedAllItems)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mLoadKeysReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } mLoadedReceived = true; mLoadedItems.Clear(); mUnknownItems.Clear(); mValues.Clear(); mKeys.Clear(); mLoadedAllItems = true; mLoadKeysReceived = true; return IPC_OK(); } mozilla::ipc::IPCResult Snapshot::RecvLoadItem(const nsString& aKey, nsString* aValue) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aValue); MOZ_ASSERT(mDatastore); if (NS_WARN_IF(mFinishReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mLoadedReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mLoadedAllItems)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (mLoadedItems.GetEntry(aKey) || mUnknownItems.GetEntry(aKey)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (auto entry = mValues.Lookup(aKey)) { *aValue = entry.Data(); entry.Remove(); } else { mDatastore->GetItem(aKey, *aValue); } if (aValue->IsVoid()) { mUnknownItems.PutEntry(aKey); } else { mLoadedItems.PutEntry(aKey); if (mLoadedItems.Count() == mTotalLength) { mLoadedItems.Clear(); mUnknownItems.Clear(); #ifdef DEBUG for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) { MOZ_ASSERT(iter.Data().IsVoid()); } #endif mValues.Clear(); mLoadedAllItems = true; } } return IPC_OK(); } mozilla::ipc::IPCResult Snapshot::RecvLoadKeys(nsTArray* aKeys) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aKeys); MOZ_ASSERT(mDatastore); if (NS_WARN_IF(mFinishReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mLoadedReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mLoadKeysReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } mLoadKeysReceived = true; if (mSavedKeys) { aKeys->AppendElements(std::move(mKeys)); } else { mDatastore->GetKeys(*aKeys); } return IPC_OK(); } mozilla::ipc::IPCResult Snapshot::RecvIncreasePeakUsage( const int64_t& aRequestedSize, const int64_t& aMinSize, int64_t* aSize) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aSize); if (NS_WARN_IF(aRequestedSize <= 0)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(aMinSize <= 0)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } if (NS_WARN_IF(mFinishReceived)) { ASSERT_UNLESS_FUZZING(); return IPC_FAIL_NO_REASON(this); } int64_t size = mDatastore->RequestUpdateUsage(aRequestedSize, aMinSize); mPeakUsage += size; *aSize = size; return IPC_OK(); } mozilla::ipc::IPCResult Snapshot::RecvPing() { AssertIsOnBackgroundThread(); // Do nothing here. This is purely a sync message allowing the child to // confirm that the actor has received previous async message. return IPC_OK(); } /******************************************************************************* * Observer ******************************************************************************/ Observer::Observer(const nsACString& aOrigin) : mOrigin(aOrigin), mActorDestroyed(false) { AssertIsOnBackgroundThread(); } Observer::~Observer() { MOZ_ASSERT(mActorDestroyed); } void Observer::Observe(Database* aDatabase, const nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& aNewValue) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatabase); Unused << SendObserve(aDatabase->GetPrincipalInfo(), aDatabase->PrivateBrowsingId(), aDocumentURI, aKey, aOldValue, aNewValue); } void Observer::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mActorDestroyed); mActorDestroyed = true; MOZ_ASSERT(gObservers); nsTArray* array; gObservers->Get(mOrigin, &array); MOZ_ASSERT(array); array->RemoveElement(this); if (array->IsEmpty()) { gObservers->Remove(mOrigin); } if (!gObservers->Count()) { gObservers = nullptr; } } mozilla::ipc::IPCResult Observer::RecvDeleteMe() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mActorDestroyed); IProtocol* mgr = Manager(); if (!PBackgroundLSObserverParent::Send__delete__(this)) { return IPC_FAIL_NO_REASON(mgr); } return IPC_OK(); } /******************************************************************************* * LSRequestBase ******************************************************************************/ LSRequestBase::LSRequestBase(nsIEventTarget* aMainEventTarget) : mMainEventTarget(aMainEventTarget), mState(State::Initial) {} LSRequestBase::~LSRequestBase() { MOZ_ASSERT_IF(MayProceedOnNonOwningThread(), mState == State::Initial || mState == State::Completed); } void LSRequestBase::Dispatch() { AssertIsOnOwningThread(); mState = State::Opening; if (mMainEventTarget) { MOZ_ALWAYS_SUCCEEDS(mMainEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); } else { MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); } } nsresult LSRequestBase::NestedRun() { return NS_OK; } void LSRequestBase::SendReadyMessage() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::SendingReadyMessage); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { MaybeSetFailureCode(NS_ERROR_FAILURE); } if (MayProceed()) { Unused << SendReady(); mState = State::WaitingForFinish; } else { Cleanup(); mState = State::Completed; } } void LSRequestBase::SendResults() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::SendingResults); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { MaybeSetFailureCode(NS_ERROR_FAILURE); } if (MayProceed()) { LSRequestResponse response; if (NS_SUCCEEDED(ResultCode())) { GetResponse(response); } else { response = ResultCode(); } Unused << PBackgroundLSRequestParent::Send__delete__(this, response); } Cleanup(); mState = State::Completed; } NS_IMETHODIMP LSRequestBase::Run() { nsresult rv; switch (mState) { case State::Opening: rv = Open(); break; case State::Nesting: rv = NestedRun(); break; case State::SendingReadyMessage: SendReadyMessage(); return NS_OK; case State::SendingResults: SendResults(); return NS_OK; default: MOZ_CRASH("Bad state!"); } if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingReadyMessage) { MaybeSetFailureCode(rv); // Must set mState before dispatching otherwise we will race with the owning // thread. mState = State::SendingReadyMessage; if (IsOnOwningThread()) { SendReadyMessage(); } else { MOZ_ALWAYS_SUCCEEDS( OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); } } return NS_OK; } void LSRequestBase::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); NoteComplete(); } mozilla::ipc::IPCResult LSRequestBase::RecvCancel() { AssertIsOnOwningThread(); IProtocol* mgr = Manager(); if (!PBackgroundLSRequestParent::Send__delete__(this, NS_ERROR_FAILURE)) { return IPC_FAIL_NO_REASON(mgr); } return IPC_OK(); } mozilla::ipc::IPCResult LSRequestBase::RecvFinish() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::WaitingForFinish); mState = State::SendingResults; // This LSRequestBase can only be held alive by the IPDL. Run() can end up // with clearing that last reference. So we need to add a self reference here. RefPtr kungFuDeathGrip = this; MOZ_ALWAYS_SUCCEEDS(this->Run()); return IPC_OK(); } /******************************************************************************* * PrepareDatastoreOp ******************************************************************************/ PrepareDatastoreOp::PrepareDatastoreOp( nsIEventTarget* aMainEventTarget, const LSRequestParams& aParams, const Maybe& aContentParentId) : LSRequestBase(aMainEventTarget), mMainEventTarget(aMainEventTarget), mLoadDataOp(nullptr), mParams(aParams.get_LSRequestPrepareDatastoreParams()), mContentParentId(aContentParentId), mPrivateBrowsingId(0), mUsage(0), mSizeOfKeys(0), mSizeOfItems(0), mNestedState(NestedState::BeforeNesting), mDatabaseNotAvailable(false), mRequestedDirectoryLock(false), mInvalidated(false) #ifdef DEBUG , mDEBUGUsage(0) #endif { MOZ_ASSERT(aParams.type() == LSRequestParams::TLSRequestPrepareDatastoreParams); } PrepareDatastoreOp::~PrepareDatastoreOp() { MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT_IF(MayProceedOnNonOwningThread(), mState == State::Initial || mState == State::Completed); MOZ_ASSERT(!mLoadDataOp); } nsresult PrepareDatastoreOp::Open() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State::Opening); MOZ_ASSERT(mNestedState == NestedState::BeforeNesting); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || !MayProceedOnNonOwningThread()) { return NS_ERROR_FAILURE; } const PrincipalInfo& principalInfo = mParams.principalInfo(); if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin); } else { MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); nsresult rv; nsCOMPtr principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &mMainThreadOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = principal->GetPrivateBrowsingId(&mPrivateBrowsingId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(principal); if (NS_WARN_IF(!mArchivedOriginScope)) { return NS_ERROR_FAILURE; } } mState = State::Nesting; mNestedState = NestedState::CheckExistingOperations; MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; } nsresult PrepareDatastoreOp::CheckExistingOperations() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::CheckExistingOperations); MOZ_ASSERT(gPrepareDatastoreOps); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { return NS_ERROR_FAILURE; } // Normally it's safe to access member variables without a mutex because even // though we hop between threads, the variables are never accessed by multiple // threads at the same time. // However, the methods OriginIsKnown and Origin can be called at any time. // So we have to make sure the member variable is set on the same thread as // those methods are called. mOrigin = mMainThreadOrigin; MOZ_ASSERT(!mOrigin.IsEmpty()); mNestedState = NestedState::CheckClosingDatastore; // See if this PrepareDatastoreOp needs to wait. bool foundThis = false; for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) { PrepareDatastoreOp* existingOp = (*gPrepareDatastoreOps)[index - 1]; if (existingOp == this) { foundThis = true; continue; } if (foundThis && existingOp->Origin() == mOrigin) { // Only one op can be delayed. MOZ_ASSERT(!existingOp->mDelayedOp); existingOp->mDelayedOp = this; return NS_OK; } } nsresult rv = CheckClosingDatastoreInternal(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::CheckClosingDatastore() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { return NS_ERROR_FAILURE; } nsresult rv = CheckClosingDatastoreInternal(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::CheckClosingDatastoreInternal() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); MOZ_ASSERT(MayProceed()); mNestedState = NestedState::PreparationPending; RefPtr datastore; if (gDatastores && (datastore = gDatastores->Get(mOrigin)) && datastore->IsClosed()) { datastore->WaitForConnectionToComplete(this); return NS_OK; } nsresult rv = BeginDatastorePreparationInternal(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::BeginDatastorePreparation() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::PreparationPending); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { return NS_ERROR_FAILURE; } nsresult rv = BeginDatastorePreparationInternal(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::BeginDatastorePreparationInternal() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::PreparationPending); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); MOZ_ASSERT(MayProceed()); if (gDatastores && (mDatastore = gDatastores->Get(mOrigin))) { MOZ_ASSERT(!mDatastore->IsClosed()); mDatastore->NoteLivePrepareDatastoreOp(this); FinishNesting(); return NS_OK; } if (QuotaManager::Get()) { nsresult rv = OpenDirectory(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } mNestedState = NestedState::QuotaManagerPending; QuotaManager::GetOrCreate(this, mMainEventTarget); return NS_OK; } nsresult PrepareDatastoreOp::QuotaManagerOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::QuotaManagerPending); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { return NS_ERROR_FAILURE; } if (NS_WARN_IF(!QuotaManager::Get())) { return NS_ERROR_FAILURE; } nsresult rv = OpenDirectory(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::OpenDirectory() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::PreparationPending || mNestedState == NestedState::QuotaManagerPending); MOZ_ASSERT(!mOrigin.IsEmpty()); MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); MOZ_ASSERT(MayProceed()); MOZ_ASSERT(QuotaManager::Get()); mNestedState = NestedState::DirectoryOpenPending; QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin, mozilla::dom::quota::Client::LS, /* aExclusive */ false, this); mRequestedDirectoryLock = true; return NS_OK; } void PrepareDatastoreOp::SendToIOThread() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); MOZ_ASSERT(MayProceed()); // Skip all disk related stuff and transition to SendingReadyMessage if we // are preparing a datastore for private browsing. // Note that we do use a directory lock for private browsing even though we // don't do any stuff on disk. The thing is that without a directory lock, // quota manager wouldn't call AbortOperations for our private browsing // origin when a clear origin operation is requested. AbortOperations // requests all databases to close and the datastore is destroyed in the end. // Any following LocalStorage API call will trigger preparation of a new // (empty) datastore. if (mPrivateBrowsingId) { FinishNesting(); return; } QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); // Must set this before dispatching otherwise we will race with the IO thread. mNestedState = NestedState::DatabaseWorkOpen; MOZ_ALWAYS_SUCCEEDS( quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)); } nsresult PrepareDatastoreOp::DatabaseWork() { AssertIsOnIOThread(); MOZ_ASSERT(mArchivedOriginScope); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || !MayProceedOnNonOwningThread()) { return NS_ERROR_FAILURE; } QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); nsresult rv; if (!gArchivedOrigins) { rv = LoadArchivedOrigins(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(gArchivedOrigins); } bool hasDataForMigration = mArchivedOriginScope->HasMatches(gArchivedOrigins); bool createIfNotExists = mParams.createIfNotExists() || hasDataForMigration; nsCOMPtr directoryEntry; rv = quotaManager->EnsureOriginIsInitialized( PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin, createIfNotExists, getter_AddRefs(directoryEntry)); if (rv == NS_ERROR_NOT_AVAILABLE) { return DatabaseNotAvailable(); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = directoryEntry->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = EnsureDirectoryEntry(directoryEntry, createIfNotExists, /* aIsDirectory */ true); if (rv == NS_ERROR_NOT_AVAILABLE) { return DatabaseNotAvailable(); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = directoryEntry->GetPath(mDirectoryPath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = directoryEntry->Append(NS_LITERAL_STRING(DATA_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool alreadyExisted; rv = EnsureDirectoryEntry(directoryEntry, createIfNotExists, /* aIsDirectory */ false, &alreadyExisted); if (rv == NS_ERROR_NOT_AVAILABLE) { return DatabaseNotAvailable(); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (alreadyExisted) { MOZ_ASSERT(gUsages); DebugOnly hasUsage = gUsages->Get(mOrigin, &mUsage); MOZ_ASSERT(hasUsage); } else { MOZ_ASSERT(mUsage == 0); InitUsageForOrigin(mOrigin, mUsage); } rv = directoryEntry->GetPath(mDatabaseFilePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr usageFile; rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; bool removedUsageFile; rv = CreateStorageConnection(directoryEntry, usageFile, mOrigin, getter_AddRefs(connection), &removedUsageFile); // removedUsageFile must be checked before rv since we may need to reset usage // even when CreateStorageConnection failed. if (removedUsageFile) { mUsage = 0; UpdateUsageForOrigin(mOrigin, mUsage); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = VerifyDatabaseInformation(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (hasDataForMigration) { MOZ_ASSERT(mUsage == 0); rv = AttachArchiveDatabase(quotaManager->GetStoragePath(), connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } int64_t newUsage; rv = GetUsage(connection, mArchivedOriginScope, &newUsage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } RefPtr quotaObject = GetQuotaObject(); MOZ_ASSERT(quotaObject); if (!quotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) { return NS_ERROR_FILE_NO_DEVICE_SPACE; } mozStorageTransaction transaction( connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); nsCOMPtr stmt; rv = connection->CreateStatement( NS_LITERAL_CSTRING("INSERT INTO data (key, value) " "SELECT key, value " "FROM webappsstore2 " "WHERE originKey = :originKey " "AND originAttributes = :originAttributes;" ), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mArchivedOriginScope->BindToStatement(stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = connection->CreateStatement( NS_LITERAL_CSTRING("UPDATE database SET usage = :usage;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("usage"), newUsage); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = connection->CreateStatement( NS_LITERAL_CSTRING("DELETE FROM webappsstore2 " "WHERE originKey = :originKey " "AND originAttributes = :originAttributes;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mArchivedOriginScope->BindToStatement(stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = transaction.Commit(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = DetachArchiveDatabase(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(gArchivedOrigins); MOZ_ASSERT(mArchivedOriginScope->HasMatches(gArchivedOrigins)); mArchivedOriginScope->RemoveMatches(gArchivedOrigins); mUsage = newUsage; MOZ_ASSERT(gUsages); MOZ_ASSERT(gUsages->Contains(mOrigin)); gUsages->Put(mOrigin, newUsage); } nsCOMPtr shadowConnection; if (!gInitializedShadowStorage) { rv = CreateShadowStorageConnection(quotaManager->GetBasePath(), getter_AddRefs(shadowConnection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } gInitializedShadowStorage = true; } // Must close connections before dispatching otherwise we might race with the // connection thread which needs to open the same databases. MOZ_ALWAYS_SUCCEEDS(connection->Close()); if (shadowConnection) { MOZ_ALWAYS_SUCCEEDS(shadowConnection->Close()); } // Must set this before dispatching otherwise we will race with the owning // thread. mNestedState = NestedState::BeginLoadData; rv = OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::DatabaseNotAvailable() { AssertIsOnIOThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen); mDatabaseNotAvailable = true; nsresult rv = FinishNestingOnNonOwningThread(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::EnsureDirectoryEntry(nsIFile* aEntry, bool aCreateIfNotExists, bool aIsDirectory, bool* aAlreadyExisted) { AssertIsOnIOThread(); MOZ_ASSERT(aEntry); bool exists; nsresult rv = aEntry->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { if (!aCreateIfNotExists) { return NS_ERROR_NOT_AVAILABLE; } if (aIsDirectory) { rv = aEntry->Create(nsIFile::DIRECTORY_TYPE, 0755); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } #ifdef DEBUG else { bool isDirectory; MOZ_ASSERT(NS_SUCCEEDED(aEntry->IsDirectory(&isDirectory))); MOZ_ASSERT(isDirectory == aIsDirectory); } #endif if (aAlreadyExisted) { *aAlreadyExisted = exists; } return NS_OK; } nsresult PrepareDatastoreOp::VerifyDatabaseInformation( mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); nsCOMPtr stmt; nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT origin " "FROM database"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasResult; rv = stmt->ExecuteStep(&hasResult); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!hasResult)) { return NS_ERROR_FILE_CORRUPTED; } nsCString origin; rv = stmt->GetUTF8String(0, origin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!QuotaManager::AreOriginsEqualOnDisk(mOrigin, origin))) { return NS_ERROR_FILE_CORRUPTED; } return NS_OK; } already_AddRefed PrepareDatastoreOp::GetQuotaObject() { MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread()); MOZ_ASSERT(!mGroup.IsEmpty()); MOZ_ASSERT(!mOrigin.IsEmpty()); MOZ_ASSERT(!mDatabaseFilePath.IsEmpty()); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); RefPtr quotaObject = quotaManager->GetQuotaObject( PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin, mDatabaseFilePath, mUsage); MOZ_ASSERT(quotaObject); return quotaObject.forget(); } nsresult PrepareDatastoreOp::BeginLoadData() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::BeginLoadData); MOZ_ASSERT(!mConnection); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { return NS_ERROR_FAILURE; } if (!gConnectionThread) { gConnectionThread = new ConnectionThread(); } mConnection = gConnectionThread->CreateConnection( mOrigin, mDirectoryPath, std::move(mArchivedOriginScope)); MOZ_ASSERT(mConnection); // Must set this before dispatching otherwise we will race with the // connection thread. mNestedState = NestedState::DatabaseWorkLoadData; // Can't assign to mLoadDataOp directly since that's a weak reference and // LoadDataOp is reference counted. RefPtr loadDataOp = new LoadDataOp(this); // This add refs loadDataOp. mConnection->Dispatch(loadDataOp); // This is cleared in LoadDataOp::Cleanup() before the load data op is // destroyed. mLoadDataOp = loadDataOp; return NS_OK; } void PrepareDatastoreOp::FinishNesting() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); // The caller holds a strong reference to us, no need for a self reference // before calling Run(). mState = State::SendingReadyMessage; mNestedState = NestedState::AfterNesting; MOZ_ALWAYS_SUCCEEDS(Run()); } nsresult PrepareDatastoreOp::FinishNestingOnNonOwningThread() { MOZ_ASSERT(!IsOnOwningThread()); MOZ_ASSERT(mState == State::Nesting); // Must set mState before dispatching otherwise we will race with the owning // thread. mState = State::SendingReadyMessage; mNestedState = NestedState::AfterNesting; nsresult rv = OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult PrepareDatastoreOp::NestedRun() { nsresult rv; switch (mNestedState) { case NestedState::CheckExistingOperations: rv = CheckExistingOperations(); break; case NestedState::CheckClosingDatastore: rv = CheckClosingDatastore(); break; case NestedState::PreparationPending: rv = BeginDatastorePreparation(); break; case NestedState::QuotaManagerPending: rv = QuotaManagerOpen(); break; case NestedState::DatabaseWorkOpen: rv = DatabaseWork(); break; case NestedState::BeginLoadData: rv = BeginLoadData(); break; default: MOZ_CRASH("Bad state!"); } if (NS_WARN_IF(NS_FAILED(rv))) { mNestedState = NestedState::AfterNesting; return rv; } return NS_OK; } void PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse) { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::SendingResults); MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); if (mDatabaseNotAvailable) { MOZ_ASSERT(!mParams.createIfNotExists()); LSRequestPrepareDatastoreResponse prepareDatastoreResponse; prepareDatastoreResponse.datastoreId() = null_t(); aResponse = prepareDatastoreResponse; return; } if (!mDatastore) { MOZ_ASSERT(mUsage == mDEBUGUsage); RefPtr quotaObject; if (mPrivateBrowsingId == 0) { quotaObject = GetQuotaObject(); MOZ_ASSERT(quotaObject); } mDatastore = new Datastore(mOrigin, mPrivateBrowsingId, mUsage, mSizeOfKeys, mSizeOfItems, mDirectoryLock.forget(), mConnection.forget(), quotaObject.forget(), mValues, mOrderedItems); mDatastore->NoteLivePrepareDatastoreOp(this); if (!gDatastores) { gDatastores = new DatastoreHashtable(); } MOZ_ASSERT(!gDatastores->Get(mOrigin)); gDatastores->Put(mOrigin, mDatastore); } uint64_t datastoreId = ++gLastDatastoreId; nsAutoPtr preparedDatastore( new PreparedDatastore(mDatastore, mContentParentId, mOrigin, datastoreId, /* aForPreload */ !mParams.createIfNotExists())); if (!gPreparedDatastores) { gPreparedDatastores = new PreparedDatastoreHashtable(); } gPreparedDatastores->Put(datastoreId, preparedDatastore); if (mInvalidated) { preparedDatastore->Invalidate(); } preparedDatastore.forget(); LSRequestPrepareDatastoreResponse prepareDatastoreResponse; prepareDatastoreResponse.datastoreId() = datastoreId; aResponse = prepareDatastoreResponse; } void PrepareDatastoreOp::Cleanup() { AssertIsOnOwningThread(); if (mDatastore) { MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(!mConnection); if (NS_FAILED(ResultCode())) { MOZ_ASSERT(!mDatastore->IsClosed()); MOZ_ASSERT(!mDatastore->HasLiveDatabases()); MOZ_ASSERT(!mDatastore->HasLivePreparedDatastores()); mDatastore->Close(); } // Make sure to release the datastore on this thread. mDatastore->NoteFinishedPrepareDatastoreOp(this); mDatastore = nullptr; CleanupMetadata(); } else if (mConnection) { // If we have a connection then the operation must have failed and there // must be a directory lock too. MOZ_ASSERT(NS_FAILED(ResultCode())); MOZ_ASSERT(mDirectoryLock); // We must close the connection on the connection thread before releasing // it on this thread. The directory lock can't be released either. nsCOMPtr callback = NewRunnableMethod("dom::OpenDatabaseOp::ConnectionClosedCallback", this, &PrepareDatastoreOp::ConnectionClosedCallback); mConnection->Close(callback); } else { // If we don't have a connection, but we do have a directory lock then the // operation must have failed or we were preloading a datastore and there // was no physical database on disk. MOZ_ASSERT_IF(mDirectoryLock, NS_FAILED(ResultCode()) || mDatabaseNotAvailable); // There's no connection, so it's safe to release the directory lock and // unregister itself from the array. mDirectoryLock = nullptr; CleanupMetadata(); } } void PrepareDatastoreOp::ConnectionClosedCallback() { AssertIsOnOwningThread(); MOZ_ASSERT(NS_FAILED(ResultCode())); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(mConnection); mConnection = nullptr; mDirectoryLock = nullptr; CleanupMetadata(); } void PrepareDatastoreOp::CleanupMetadata() { AssertIsOnOwningThread(); if (mDelayedOp) { MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget())); } MOZ_ASSERT(gPrepareDatastoreOps); gPrepareDatastoreOps->RemoveElement(this); if (gPrepareDatastoreOps->IsEmpty()) { gPrepareDatastoreOps = nullptr; } } NS_IMPL_ISUPPORTS_INHERITED0(PrepareDatastoreOp, LSRequestBase) void PrepareDatastoreOp::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); LSRequestBase::ActorDestroy(aWhy); if (mLoadDataOp) { mLoadDataOp->NoteComplete(); } } void PrepareDatastoreOp::DirectoryLockAcquired(DirectoryLock* aLock) { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending); MOZ_ASSERT(!mDirectoryLock); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { MaybeSetFailureCode(NS_ERROR_FAILURE); FinishNesting(); return; } mDirectoryLock = aLock; SendToIOThread(); } void PrepareDatastoreOp::DirectoryLockFailed() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Nesting); MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending); MOZ_ASSERT(!mDirectoryLock); MaybeSetFailureCode(NS_ERROR_FAILURE); FinishNesting(); } nsresult PrepareDatastoreOp::LoadDataOp::DoDatastoreWork() { AssertIsOnConnectionThread(); MOZ_ASSERT(mConnection); MOZ_ASSERT(mPrepareDatastoreOp); MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting); MOZ_ASSERT(mPrepareDatastoreOp->mNestedState == NestedState::DatabaseWorkLoadData); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || !MayProceedOnNonOwningThread()) { return NS_ERROR_FAILURE; } Connection::CachedStatement stmt; nsresult rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("SELECT key, value " "FROM data;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasResult; while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult) { nsString key; rv = stmt->GetString(0, key); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsString value; rv = stmt->GetString(1, value); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mPrepareDatastoreOp->mValues.Put(key, value); auto item = mPrepareDatastoreOp->mOrderedItems.AppendElement(); item->key() = key; item->value() = value; mPrepareDatastoreOp->mSizeOfKeys += key.Length(); mPrepareDatastoreOp->mSizeOfItems += key.Length() + value.Length(); #ifdef DEBUG mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.Length(); #endif } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } void PrepareDatastoreOp::LoadDataOp::OnSuccess() { AssertIsOnOwningThread(); MOZ_ASSERT(mPrepareDatastoreOp); MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting); MOZ_ASSERT(mPrepareDatastoreOp->mNestedState == NestedState::DatabaseWorkLoadData); MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this); mPrepareDatastoreOp->FinishNesting(); } void PrepareDatastoreOp::LoadDataOp::OnFailure(nsresult aResultCode) { AssertIsOnOwningThread(); MOZ_ASSERT(mPrepareDatastoreOp); MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting); MOZ_ASSERT(mPrepareDatastoreOp->mNestedState == NestedState::DatabaseWorkLoadData); MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this); mPrepareDatastoreOp->SetFailureCode(aResultCode); mPrepareDatastoreOp->FinishNesting(); } void PrepareDatastoreOp::LoadDataOp::Cleanup() { AssertIsOnOwningThread(); MOZ_ASSERT(mPrepareDatastoreOp); MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this); mPrepareDatastoreOp->mLoadDataOp = nullptr; mPrepareDatastoreOp = nullptr; ConnectionDatastoreOperationBase::Cleanup(); } /******************************************************************************* * PrepareObserverOp ******************************************************************************/ PrepareObserverOp::PrepareObserverOp(nsIEventTarget* aMainEventTarget, const LSRequestParams& aParams) : LSRequestBase(aMainEventTarget), mParams(aParams.get_LSRequestPrepareObserverParams()) { MOZ_ASSERT(aParams.type() == LSRequestParams::TLSRequestPrepareObserverParams); } nsresult PrepareObserverOp::Open() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State::Opening); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || !MayProceedOnNonOwningThread()) { return NS_ERROR_FAILURE; } const PrincipalInfo& principalInfo = mParams.principalInfo(); if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { QuotaManager::GetInfoForChrome(nullptr, nullptr, &mOrigin); } else { MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); nsresult rv; nsCOMPtr principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mState = State::SendingReadyMessage; MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; } void PrepareObserverOp::GetResponse(LSRequestResponse& aResponse) { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::SendingResults); MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); uint64_t observerId = ++gLastObserverId; RefPtr observer = new Observer(mOrigin); if (!gPreparedObsevers) { gPreparedObsevers = new PreparedObserverHashtable(); } gPreparedObsevers->Put(observerId, observer); LSRequestPrepareObserverResponse prepareObserverResponse; prepareObserverResponse.observerId() = observerId; aResponse = prepareObserverResponse; } /******************************************************************************* + * LSSimpleRequestBase + ******************************************************************************/ LSSimpleRequestBase::LSSimpleRequestBase() : mState(State::Initial) {} LSSimpleRequestBase::~LSSimpleRequestBase() { MOZ_ASSERT_IF(MayProceedOnNonOwningThread(), mState == State::Initial || mState == State::Completed); } void LSSimpleRequestBase::Dispatch() { AssertIsOnOwningThread(); mState = State::Opening; MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); } void LSSimpleRequestBase::SendResults() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::SendingResults); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || !MayProceed()) { MaybeSetFailureCode(NS_ERROR_FAILURE); } if (MayProceed()) { LSSimpleRequestResponse response; if (NS_SUCCEEDED(ResultCode())) { GetResponse(response); } else { response = ResultCode(); } Unused << PBackgroundLSSimpleRequestParent::Send__delete__(this, response); } mState = State::Completed; } NS_IMETHODIMP LSSimpleRequestBase::Run() { nsresult rv; switch (mState) { case State::Opening: rv = Open(); break; case State::SendingResults: SendResults(); return NS_OK; default: MOZ_CRASH("Bad state!"); } if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) { MaybeSetFailureCode(rv); // Must set mState before dispatching otherwise we will race with the owning // thread. mState = State::SendingResults; if (IsOnOwningThread()) { SendResults(); } else { MOZ_ALWAYS_SUCCEEDS( OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); } } return NS_OK; } void LSSimpleRequestBase::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); NoteComplete(); } /******************************************************************************* * PreloadedOp ******************************************************************************/ PreloadedOp::PreloadedOp(const LSSimpleRequestParams& aParams) : mParams(aParams.get_LSSimpleRequestPreloadedParams()) { MOZ_ASSERT(aParams.type() == LSSimpleRequestParams::TLSSimpleRequestPreloadedParams); } nsresult PreloadedOp::Open() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State::Opening); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || !MayProceedOnNonOwningThread()) { return NS_ERROR_FAILURE; } const PrincipalInfo& principalInfo = mParams.principalInfo(); if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { QuotaManager::GetInfoForChrome(nullptr, nullptr, &mOrigin); } else { MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); nsresult rv; nsCOMPtr principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mState = State::SendingResults; MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; } void PreloadedOp::GetResponse(LSSimpleRequestResponse& aResponse) { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::SendingResults); MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); bool preloaded; RefPtr datastore; if (gDatastores && (datastore = gDatastores->Get(mOrigin)) && !datastore->IsClosed()) { preloaded = true; } else { preloaded = false; } LSSimpleRequestPreloadedResponse preloadedResponse; preloadedResponse.preloaded() = preloaded; aResponse = preloadedResponse; } /******************************************************************************* * ArchivedOriginScope ******************************************************************************/ // static ArchivedOriginScope* ArchivedOriginScope::CreateFromOrigin( nsIPrincipal* aPrincipal) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal); nsCString originAttrSuffix; nsCString originKey; nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } return new ArchivedOriginScope( std::move(Origin(originAttrSuffix, originKey))); } // static ArchivedOriginScope* ArchivedOriginScope::CreateFromPrefix( nsIPrincipal* aPrincipal) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal); nsCString originAttrSuffix; nsCString originKey; nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } return new ArchivedOriginScope(std::move(Prefix(originKey))); } // static ArchivedOriginScope* ArchivedOriginScope::CreateFromPattern( const OriginAttributesPattern& aPattern) { return new ArchivedOriginScope(std::move(Pattern(aPattern))); } // static ArchivedOriginScope* ArchivedOriginScope::CreateFromNull() { return new ArchivedOriginScope(std::move(Null())); } void ArchivedOriginScope::GetBindingClause(nsACString& aBindingClause) const { struct Matcher { nsACString* mBindingClause; explicit Matcher(nsACString* aBindingClause) : mBindingClause(aBindingClause) {} void match(const Origin& aOrigin) { *mBindingClause = NS_LITERAL_CSTRING( " WHERE originKey = :originKey " "AND originAttributes = :originAttributes"); } void match(const Prefix& aPrefix) { *mBindingClause = NS_LITERAL_CSTRING(" WHERE originKey = :originKey"); } void match(const Pattern& aPattern) { *mBindingClause = NS_LITERAL_CSTRING( " WHERE originAttributes MATCH :originAttributesPattern"); } void match(const Null& aNull) { *mBindingClause = EmptyCString(); } }; mData.match(Matcher(&aBindingClause)); } nsresult ArchivedOriginScope::BindToStatement( mozIStorageStatement* aStmt) const { MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread()); MOZ_ASSERT(aStmt); struct Matcher { mozIStorageStatement* mStmt; explicit Matcher(mozIStorageStatement* aStmt) : mStmt(aStmt) {} nsresult match(const Origin& aOrigin) { nsresult rv = mStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"), aOrigin.OriginNoSuffix()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"), aOrigin.OriginSuffix()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult match(const Prefix& aPrefix) { nsresult rv = mStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"), aPrefix.OriginNoSuffix()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult match(const Pattern& aPattern) { nsresult rv = mStmt->BindUTF8StringByName( NS_LITERAL_CSTRING("originAttributesPattern"), NS_LITERAL_CSTRING("pattern1")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult match(const Null& aNull) { return NS_OK; } }; nsresult rv = mData.match(Matcher(aStmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } bool ArchivedOriginScope::HasMatches( ArchivedOriginHashtable* aHashtable) const { AssertIsOnIOThread(); MOZ_ASSERT(aHashtable); struct Matcher { ArchivedOriginHashtable* mHashtable; explicit Matcher(ArchivedOriginHashtable* aHashtable) : mHashtable(aHashtable) {} bool match(const Origin& aOrigin) { nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(), aOrigin.OriginNoSuffix()); ArchivedOriginInfo* archivedOriginInfo; return mHashtable->Get(hashKey, &archivedOriginInfo); } bool match(const Prefix& aPrefix) { for (auto iter = mHashtable->ConstIter(); !iter.Done(); iter.Next()) { ArchivedOriginInfo* archivedOriginInfo = iter.Data(); if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) { return true; } } return false; } bool match(const Pattern& aPattern) { for (auto iter = mHashtable->ConstIter(); !iter.Done(); iter.Next()) { ArchivedOriginInfo* archivedOriginInfo = iter.Data(); if (aPattern.GetPattern().Matches( archivedOriginInfo->mOriginAttributes)) { return true; } } return false; } bool match(const Null& aNull) { return mHashtable->Count(); } }; return mData.match(Matcher(aHashtable)); } void ArchivedOriginScope::RemoveMatches( ArchivedOriginHashtable* aHashtable) const { AssertIsOnIOThread(); MOZ_ASSERT(aHashtable); struct Matcher { ArchivedOriginHashtable* mHashtable; explicit Matcher(ArchivedOriginHashtable* aHashtable) : mHashtable(aHashtable) {} void match(const Origin& aOrigin) { nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(), aOrigin.OriginNoSuffix()); mHashtable->Remove(hashKey); } void match(const Prefix& aPrefix) { for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) { ArchivedOriginInfo* archivedOriginInfo = iter.Data(); if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) { iter.Remove(); } } } void match(const Pattern& aPattern) { for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) { ArchivedOriginInfo* archivedOriginInfo = iter.Data(); if (aPattern.GetPattern().Matches( archivedOriginInfo->mOriginAttributes)) { iter.Remove(); } } } void match(const Null& aNull) { mHashtable->Clear(); } }; mData.match(Matcher(aHashtable)); } /******************************************************************************* * ArchivedOriginScopeHelper ******************************************************************************/ nsresult ArchivedOriginScopeHelper::BlockAndReturnArchivedOriginScope( nsAutoPtr& aArchivedOriginScope) { AssertIsOnIOThread(); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); mozilla::MonitorAutoLock lock(mMonitor); while (mWaiting) { lock.Wait(); } if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) { return mMainThreadResultCode; } aArchivedOriginScope = std::move(mArchivedOriginScope); return NS_OK; } nsresult ArchivedOriginScopeHelper::RunOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), mSpec); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr principal = BasePrincipal::CreateCodebasePrincipal(uri, mAttrs); if (NS_WARN_IF(!principal)) { return NS_ERROR_FAILURE; } if (mPrefix) { mArchivedOriginScope = ArchivedOriginScope::CreateFromPrefix(principal); } else { mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(principal); } if (NS_WARN_IF(!mArchivedOriginScope)) { return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP ArchivedOriginScopeHelper::Run() { MOZ_ASSERT(NS_IsMainThread()); nsresult rv = RunOnMainThread(); if (NS_WARN_IF(NS_FAILED(rv))) { mMainThreadResultCode = rv; } mozilla::MonitorAutoLock lock(mMonitor); MOZ_ASSERT(mWaiting); mWaiting = false; lock.Notify(); return NS_OK; } /******************************************************************************* * QuotaClient ******************************************************************************/ QuotaClient* QuotaClient::sInstance = nullptr; QuotaClient::QuotaClient() : mShadowDatabaseMutex("LocalStorage mShadowDatabaseMutex"), mShutdownRequested(false) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!sInstance, "We expect this to be a singleton!"); sInstance = this; } QuotaClient::~QuotaClient() { AssertIsOnBackgroundThread(); MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!"); sInstance = nullptr; } // static nsresult QuotaClient::Initialize() { MOZ_ASSERT(NS_IsMainThread()); nsresult rv = Observer::Initialize(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } mozilla::dom::quota::Client::Type QuotaClient::GetType() { return QuotaClient::LS; } nsresult QuotaClient::InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) { AssertIsOnIOThread(); MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); nsCOMPtr directory; nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin, getter_AddRefs(directory)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetDirForOrigin); return rv; } MOZ_ASSERT(directory); rv = directory->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Append); return rv; } #ifdef DEBUG bool exists; rv = directory->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Exists); return rv; } MOZ_ASSERT(exists); #endif nsString directoryPath; rv = directory->GetPath(directoryPath); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetPath); return rv; } nsCOMPtr usageFile; rv = GetUsageFile(directoryPath, getter_AddRefs(usageFile)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetUsageFile); return rv; } bool usageFileExists; bool isDirectory; rv = usageFile->IsDirectory(&isDirectory); if (rv != NS_ERROR_FILE_NOT_FOUND && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_IsDirectory); return rv; } if (NS_WARN_IF(isDirectory)) { REPORT_TELEMETRY_INIT_ERR(kInternalError, LS_UnexpectedDir); return NS_ERROR_FAILURE; } usageFileExists = true; } else { usageFileExists = false; } nsCOMPtr usageJournalFile; rv = GetUsageJournalFile(directoryPath, getter_AddRefs(usageJournalFile)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetUsageForJFile); return rv; } rv = usageJournalFile->IsDirectory(&isDirectory); if (rv != NS_ERROR_FILE_NOT_FOUND && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_IsDirectory2); return rv; } if (NS_WARN_IF(isDirectory)) { REPORT_TELEMETRY_INIT_ERR(kInternalError, LS_UnexpectedDir2); return NS_ERROR_FAILURE; } if (usageFileExists) { rv = usageFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Remove); return rv; } usageFileExists = false; } rv = usageJournalFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Remove2); return rv; } } nsCOMPtr file; rv = directory->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Clone); return rv; } rv = file->Append(NS_LITERAL_STRING(DATA_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Append2); return rv; } rv = file->IsDirectory(&isDirectory); if (rv != NS_ERROR_FILE_NOT_FOUND && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_IsDirectory3); return rv; } if (NS_WARN_IF(isDirectory)) { REPORT_TELEMETRY_INIT_ERR(kInternalError, LS_UnexpectedDir3); return NS_ERROR_FAILURE; } int64_t usage; rv = LoadUsageFile(usageFile, &usage); if (NS_WARN_IF(NS_FAILED(rv))) { nsCOMPtr connection; bool dummy; rv = CreateStorageConnection(file, usageFile, aOrigin, getter_AddRefs(connection), &dummy); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_CreateConnection); return rv; } rv = GetUsage(connection, /* aArchivedOriginScope */ nullptr, &usage); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetUsage); return rv; } rv = UpdateUsageFile(usageFile, usageJournalFile, usage); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_UpdateUsageFile); return rv; } rv = usageJournalFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Remove3); return rv; } } MOZ_ASSERT(usage >= 0); InitUsageForOrigin(aOrigin, usage); aUsageInfo->AppendToDatabaseUsage(uint64_t(usage)); } else if (usageFileExists) { rv = usageFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Remove4); return rv; } } // Report unknown files in debug builds, but don't fail, just warn. #ifdef DEBUG nsCOMPtr directoryEntries; rv = directory->GetDirectoryEntries(getter_AddRefs(directoryEntries)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetDirEntries); return rv; } if (!directoryEntries) { return NS_OK; } while (true) { if (aCanceled) { break; } nsCOMPtr file; rv = directoryEntries->GetNextFile(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetNextFile); return rv; } if (!file) { break; } nsString leafName; rv = file->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_GetLeafName); return rv; } // Don't need to check for USAGE_JOURNAL_FILE_NAME. We removed it above // (if there was any). if (leafName.EqualsLiteral(DATA_FILE_NAME) || leafName.EqualsLiteral(USAGE_FILE_NAME)) { // Don't need to check if it is a directory or file. We did that above. continue; } if (leafName.EqualsLiteral(JOURNAL_FILE_NAME)) { bool isDirectory; rv = file->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_IsDirectory4); return rv; } if (!isDirectory) { continue; } } LS_WARNING("Something (%s) in the directory that doesn't belong!", NS_ConvertUTF16toUTF8(leafName).get()); } #endif return NS_OK; } nsresult QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) { AssertIsOnIOThread(); MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT); MOZ_ASSERT(aUsageInfo); // We can't open the database at this point, since it can be already used // by the connection thread. Use the cached value instead. if (gUsages) { int64_t usage; if (gUsages->Get(aOrigin, &usage)) { MOZ_ASSERT(usage >= 0); aUsageInfo->AppendToDatabaseUsage(usage); } } return NS_OK; } nsresult QuotaClient::AboutToClearOrigins( const Nullable& aPersistenceType, const OriginScope& aOriginScope) { AssertIsOnIOThread(); // This method is not called when the clearing is triggered by the eviction // process. It's on purpose to avoid a problem with the origin access time // which can be described as follows: // When there's a storage pressure condition and quota manager starts // collecting origins for eviction, there can be an origin that hasn't been // touched for long time. However, the old implementation of local storage // could have touched the origin only recently and the new implementation // hasn't had a chance to create a new per origin database for it yet (the // data is still in the archive database), so the origin access time hasn't // been updated either. In the end, the origin would be evicted despite the // fact that there was recent local storage activity. // So this method clears the archived data and shadow database entries for // given origin scope, but only if it's a privacy-related origin clearing. if (!aPersistenceType.IsNull() && aPersistenceType.Value() != PERSISTENCE_TYPE_DEFAULT) { return NS_OK; } bool shadowWrites = gShadowWrites; nsAutoPtr archivedOriginScope; nsresult rv = CreateArchivedOriginScope(aOriginScope, archivedOriginScope); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!gArchivedOrigins) { rv = LoadArchivedOrigins(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(gArchivedOrigins); } bool hasDataForRemoval = archivedOriginScope->HasMatches(gArchivedOrigins); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); nsString basePath = quotaManager->GetBasePath(); { MutexAutoLock shadowDatabaseLock(mShadowDatabaseMutex); nsCOMPtr connection; if (gInitializedShadowStorage) { rv = GetShadowStorageConnection(basePath, getter_AddRefs(connection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { rv = CreateShadowStorageConnection(basePath, getter_AddRefs(connection)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } gInitializedShadowStorage = true; } if (hasDataForRemoval) { rv = AttachArchiveDatabase(quotaManager->GetStoragePath(), connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (archivedOriginScope->IsPattern()) { nsCOMPtr function( new MatchFunction(archivedOriginScope->GetPattern())); rv = connection->CreateFunction(NS_LITERAL_CSTRING("match"), 2, function); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } nsCOMPtr stmt; rv = connection->CreateStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (shadowWrites) { rv = PerformDelete(connection, NS_LITERAL_CSTRING("main"), archivedOriginScope); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (hasDataForRemoval) { rv = PerformDelete(connection, NS_LITERAL_CSTRING("archive"), archivedOriginScope); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } rv = connection->CreateStatement(NS_LITERAL_CSTRING("COMMIT;"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } stmt = nullptr; if (archivedOriginScope->IsPattern()) { rv = connection->RemoveFunction(NS_LITERAL_CSTRING("match")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (hasDataForRemoval) { rv = DetachArchiveDatabase(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(gArchivedOrigins); MOZ_ASSERT(archivedOriginScope->HasMatches(gArchivedOrigins)); archivedOriginScope->RemoveMatches(gArchivedOrigins); } rv = connection->Close(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (aOriginScope.IsNull()) { nsCOMPtr shadowFile; rv = GetShadowFile(basePath, getter_AddRefs(shadowFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = shadowFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } gInitializedShadowStorage = false; } return NS_OK; } void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin) { AssertIsOnIOThread(); if (aPersistenceType != PERSISTENCE_TYPE_DEFAULT) { return; } if (gUsages) { gUsages->Remove(aOrigin); } } void QuotaClient::ReleaseIOThreadObjects() { AssertIsOnIOThread(); gUsages = nullptr; // Delete archived origins hashtable since QuotaManager clears the whole // storage directory including ls-archive.sqlite. gArchivedOrigins = nullptr; } void QuotaClient::AbortOperations(const nsACString& aOrigin) { AssertIsOnBackgroundThread(); // A PrepareDatastoreOp object could already acquire a directory lock for // the given origin. Its last step is creation of a Datastore object (which // will take ownership of the directory lock) and a PreparedDatastore object // which keeps the Datastore alive until a database actor is created. // We need to invalidate the PreparedDatastore object when it's created, // otherwise the Datastore object can block the origin clear operation for // long time. It's not a problem that we don't fail the PrepareDatastoreOp // immediatelly (avoiding the creation of the Datastore and PreparedDatastore // object). We will call RequestAllowToClose on the database actor once it's // created and the child actor will respond by sending AllowToClose which // will close the Datastore on the parent side (the closing releases the // directory lock). if (gPrepareDatastoreOps) { for (PrepareDatastoreOp* prepareDatastoreOp : *gPrepareDatastoreOps) { MOZ_ASSERT(prepareDatastoreOp); // Explicitely check if a directory lock has been requested. // Origin clearing can't be blocked by this PrepareDatastoreOp if it // hasn't requested a directory lock yet, so we can just ignore it. // This will also guarantee that PrepareDatastoreOp has a known origin. // And it also ensures that the ordering is right. Without the check we // could invalidate ops whose directory locks were requested after we // requested a directory lock for origin clearing. if (!prepareDatastoreOp->RequestedDirectoryLock()) { continue; } MOZ_ASSERT(prepareDatastoreOp->OriginIsKnown()); if (aOrigin.IsVoid() || prepareDatastoreOp->Origin() == aOrigin) { prepareDatastoreOp->Invalidate(); } } } if (gPreparedDatastores) { for (auto iter = gPreparedDatastores->ConstIter(); !iter.Done(); iter.Next()) { PreparedDatastore* preparedDatastore = iter.Data(); MOZ_ASSERT(preparedDatastore); if (aOrigin.IsVoid() || preparedDatastore->Origin() == aOrigin) { preparedDatastore->Invalidate(); } } } if (gLiveDatabases) { for (Database* database : *gLiveDatabases) { if (aOrigin.IsVoid() || database->Origin() == aOrigin) { // TODO: This just allows the database to close, but we can actually // set a flag to abort any existing operations, so we can // eventually close faster. database->RequestAllowToClose(); } } } } void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) { AssertIsOnBackgroundThread(); if (gLiveDatabases) { for (Database* database : *gLiveDatabases) { if (database->IsOwnedByProcess(aContentParentId)) { database->RequestAllowToClose(); } } } } void QuotaClient::StartIdleMaintenance() { AssertIsOnBackgroundThread(); } void QuotaClient::StopIdleMaintenance() { AssertIsOnBackgroundThread(); } void QuotaClient::ShutdownWorkThreads() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mShutdownRequested); mShutdownRequested = true; // gPrepareDatastoreOps are short lived objects running a state machine. // The shutdown flag is checked between states, so we don't have to notify // all the objects here. // Allocation of a new PrepareDatastoreOp object is prevented once the // shutdown flag is set. // When the last PrepareDatastoreOp finishes, the gPrepareDatastoreOps array // is destroyed. if (gPreparedDatastores) { gPreparedDatastores->Clear(); gPreparedDatastores = nullptr; } if (gLiveDatabases) { for (Database* database : *gLiveDatabases) { database->RequestAllowToClose(); } } if (gPreparedObsevers) { gPreparedObsevers->Clear(); gPreparedObsevers = nullptr; } // This should release any local storage related quota objects or directory // locks. MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { // Don't have to check gPreparedDatastores since we nulled it out above. return !gPrepareDatastoreOps && !gDatastores && !gLiveDatabases; })); // And finally, shutdown the connection thread. if (gConnectionThread) { gConnectionThread->Shutdown(); gConnectionThread = nullptr; } } nsresult QuotaClient::CreateArchivedOriginScope( const OriginScope& aOriginScope, nsAutoPtr& aArchivedOriginScope) { AssertIsOnIOThread(); nsresult rv; nsAutoPtr archivedOriginScope; if (aOriginScope.IsOrigin()) { nsCString spec; OriginAttributes attrs; if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOrigin(), spec, &attrs))) { return NS_ERROR_FAILURE; } RefPtr helper = new ArchivedOriginScopeHelper(spec, attrs, /* aPrefix */ false); rv = helper->BlockAndReturnArchivedOriginScope(archivedOriginScope); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else if (aOriginScope.IsPrefix()) { nsCString spec; OriginAttributes attrs; if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOriginNoSuffix(), spec, &attrs))) { return NS_ERROR_FAILURE; } RefPtr helper = new ArchivedOriginScopeHelper(spec, attrs, /* aPrefix */ true); rv = helper->BlockAndReturnArchivedOriginScope(archivedOriginScope); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else if (aOriginScope.IsPattern()) { archivedOriginScope = ArchivedOriginScope::CreateFromPattern(aOriginScope.GetPattern()); } else { MOZ_ASSERT(aOriginScope.IsNull()); archivedOriginScope = ArchivedOriginScope::CreateFromNull(); } MOZ_ASSERT(archivedOriginScope); aArchivedOriginScope = std::move(archivedOriginScope); return NS_OK; } nsresult QuotaClient::PerformDelete( mozIStorageConnection* aConnection, const nsACString& aSchemaName, ArchivedOriginScope* aArchivedOriginScope) const { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); MOZ_ASSERT(aArchivedOriginScope); nsresult rv; nsCString bindingClause; aArchivedOriginScope->GetBindingClause(bindingClause); nsCOMPtr stmt; rv = aConnection->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM ") + aSchemaName + NS_LITERAL_CSTRING(".webappsstore2") + bindingClause + NS_LITERAL_CSTRING(";"), getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aArchivedOriginScope->BindToStatement(stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } // static nsresult QuotaClient::Observer::Initialize() { MOZ_ASSERT(NS_IsMainThread()); RefPtr observer = new Observer(); nsresult rv = observer->Init(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult QuotaClient::Observer::Init() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = services::GetObserverService(); if (NS_WARN_IF(!obs)) { return NS_ERROR_FAILURE; } nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = obs->AddObserver(this, kPrivateBrowsingObserverTopic, false); if (NS_WARN_IF(NS_FAILED(rv))) { obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); return rv; } return NS_OK; } nsresult QuotaClient::Observer::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = services::GetObserverService(); if (NS_WARN_IF(!obs)) { return NS_ERROR_FAILURE; } MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kPrivateBrowsingObserverTopic)); MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)); // In general, the instance will have died after the latter removal call, so // it's not safe to do anything after that point. // However, Shutdown is currently called from Observe which is called by the // Observer Service which holds a strong reference to the observer while the // Observe method is being called. return NS_OK; } NS_IMPL_ISUPPORTS(QuotaClient::Observer, nsIObserver) NS_IMETHODIMP QuotaClient::Observer::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(NS_IsMainThread()); nsresult rv; if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) { PBackgroundChild* backgroundActor = BackgroundChild::GetOrCreateForCurrentThread(); if (NS_WARN_IF(!backgroundActor)) { return NS_ERROR_FAILURE; } if (NS_WARN_IF(!backgroundActor->SendLSClearPrivateBrowsing())) { return NS_ERROR_FAILURE; } return NS_OK; } if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { rv = Shutdown(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } NS_WARNING("Unknown observer topic!"); return NS_OK; } NS_IMPL_ISUPPORTS(QuotaClient::MatchFunction, mozIStorageFunction) NS_IMETHODIMP QuotaClient::MatchFunction::OnFunctionCall( mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { AssertIsOnIOThread(); MOZ_ASSERT(aFunctionArguments); MOZ_ASSERT(aResult); nsCString suffix; nsresult rv = aFunctionArguments->GetUTF8String(1, suffix); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } OriginAttributes oa; if (NS_WARN_IF(!oa.PopulateFromSuffix(suffix))) { return NS_ERROR_FAILURE; } bool result = mPattern.Matches(oa); RefPtr outVar(new nsVariant()); rv = outVar->SetAsBool(result); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } outVar.forget(aResult); return NS_OK; } } // namespace dom } // namespace mozilla