gecko-dev/dom/localstorage/ActorsParent.cpp

9731 строка
267 KiB
C++

/* -*- 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/StaticPrefs_dom.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ClientManagerService.h"
#include "mozilla/dom/LSWriteOptimizer.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/CheckedUnsafePtr.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 "mozilla/Logging.h"
#include "mozilla/storage/Variant.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsExceptionHandler.h"
#include "nsInterfaceHashtable.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.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
#define LS_LOG_TEST() MOZ_LOG_TEST(GetLocalStorageLogger(), LogLevel::Info)
#define LS_LOG(_args) MOZ_LOG(GetLocalStorageLogger(), LogLevel::Info, _args)
#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 Observer;
class PrepareDatastoreOp;
class PreparedDatastore;
class QuotaClient;
class Snapshot;
typedef nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>
ArchivedOriginHashtable;
/*******************************************************************************
* Constants
******************************************************************************/
// Major schema version. Bump for almost everything.
const uint32_t kMajorSchemaVersion = 4;
// 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 kDefaultNextGen = false;
const uint32_t kDefaultShadowWrites = true;
const uint32_t kDefaultSnapshotPrefill = 16384;
const uint32_t kDefaultSnapshotGradualPrefill = 4096;
const uint32_t kDefaultClientValidation = true;
/**
*
*/
const char kNextGenPref[] = "dom.storage.next_gen";
/**
* 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";
/**
* When a specific value is requested by an LSSnapshot that is not already fully
* populated, gradual prefill is used. This preference specifies the number of
* bytes to be used to send values beyond the specific value that is requested.
* (The size of the explicitly requested value does not impact this preference.)
* Setting the value to 0 disables gradual prefill. Tests may set this value to
* -1 which is converted to INT_MAX in order to cause gradual prefill to send
* all values not previously sent.
*/
const char kSnapshotGradualPrefillPref[] =
"dom.storage.snapshot_gradual_prefill";
const char kClientValidationPref[] = "dom.storage.client_validation";
/**
* 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;
/**
* Automatically kill database actors if LocalStorage shutdown takes this long.
*/
#define SHUTDOWN_FORCE_KILL_TIMEOUT_MS 5000
/**
* Automatically crash the browser if LocalStorage shutdown takes this long.
* We've chosen a value that is longer than the value for QuotaManager shutdown
* timer which is currently set to 30 seconds. We've also chosen a value that
* is long enough that it is unlikely for the problem to be falsely triggered by
* slow system I/O. We've also chosen a value long enough so that automated
* tests should time out and fail if LocalStorage shutdown hangs. Also, this
* value is long enough so that testers can notice the LocalStorage shutdown
* hang; we want to know about the hangs, not hide them. On the other hand this
* value is less than 60 seconds which is used by nsTerminator to crash a hung
* main process.
*/
#define SHUTDOWN_FORCE_CRASH_TIMEOUT_MS 45000
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) {
MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
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"
", utf16Length INTEGER NOT NULL DEFAULT 0"
", 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 UpgradeSchemaFrom2_0To3_0(mozIStorageConnection* aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE data ADD COLUMN utf16Length INTEGER NOT NULL DEFAULT 0;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("UPDATE data SET utf16Length = utf16Length(value);"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(3, 0));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult UpgradeSchemaFrom3_0To4_0(mozIStorageConnection* aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(4, 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) {
MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
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<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> 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<mozIStorageStatement> 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((4 << 4) + 0),
"Upgrade function needed due to schema version increase.");
while (schemaVersion != kSQLiteSchemaVersion) {
if (schemaVersion == MakeSchemaVersion(1, 0)) {
rv = UpgradeSchemaFrom1_0To2_0(connection);
} else if (schemaVersion == MakeSchemaVersion(2, 0)) {
rv = UpgradeSchemaFrom2_0To3_0(connection);
} else if (schemaVersion == MakeSchemaVersion(3, 0)) {
rv = UpgradeSchemaFrom3_0To4_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<mozIStorageStatement> 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<nsIFile> 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<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> 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<nsIFile> 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<nsIFile> archiveFile;
nsresult rv = GetArchiveFile(aStoragePath, getter_AddRefs(archiveFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// QuotaManager ensures this file always exists.
DebugOnly<bool> 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<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> 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<nsIFile> 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<mozIStorageStatement> 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<nsIFile> 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) {
MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
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<mozIStorageStatement> 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<int32_t>(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) {
MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
MOZ_ASSERT(!aBasePath.IsEmpty());
MOZ_ASSERT(aConnection);
nsCOMPtr<nsIFile> shadowFile;
nsresult rv = GetShadowFile(aBasePath, getter_AddRefs(shadowFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> 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<nsIFile> 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<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> 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<nsIFile> 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<mozIStorageStatement> 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<nsIFile> 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<nsIFile> 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);
bool isDirectory;
nsresult rv = aUsageJournalFile->IsDirectory(&isDirectory);
if (rv != NS_ERROR_FILE_NOT_FOUND &&
rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(isDirectory)) {
return NS_ERROR_FAILURE;
}
} else {
rv = aUsageJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
nsCOMPtr<nsIOutputStream> stream;
rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aUsageFile);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIBinaryOutputStream> 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<nsIInputStream> stream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aUsageFile);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIInputStream> bufferedStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
stream.forget(), 16);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIBinaryInputStream> 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 `Datastore`. 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 DatastoreWriteOptimizer final : public LSWriteOptimizer<LSValue> {
public:
void ApplyAndReset(nsTArray<LSItemInfo>& aOrderedItems);
};
/**
* Coalescing manipulation queue used by `Connection`. Used by `Connection` to
* buffer and coalesce manipulations applied to the Datastore in batches by
* Snapshot Checkpointing until flushed to disk.
*/
class ConnectionWriteOptimizer final : public LSWriteOptimizer<LSValue> {
public:
nsresult Perform(Connection* aConnection, bool aShadowWrites,
int64_t& aOutUsage);
private:
/**
* Handlers for specific mutations. Each method 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.)
*/
nsresult PerformInsertOrUpdate(Connection* aConnection, bool aShadowWrites,
const nsAString& aKey, const LSValue& aValue);
nsresult PerformDelete(Connection* aConnection, bool aShadowWrites,
const nsAString& aKey);
nsresult PerformTruncate(Connection* aConnection, bool aShadowWrites);
};
class DatastoreOperationBase : public Runnable {
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
nsresult mResultCode;
Atomic<bool> 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(&current)) &&
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<Connection> mConnection;
/**
* This boolean flag is used by the CloseOp to avoid creating empty databases.
*/
const bool mEnsureStorageConnection;
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,
bool aEnsureStorageConnection = true);
~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 InitStorageAndOriginHelper;
class FlushOp;
class CloseOp;
RefPtr<ConnectionThread> mConnectionThread;
RefPtr<QuotaClient> mQuotaClient;
nsCOMPtr<nsITimer> mFlushTimer;
nsCOMPtr<mozIStorageConnection> mStorageConnection;
nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
mCachedStatements;
ConnectionWriteOptimizer mWriteOptimizer;
const nsCString mSuffix;
const nsCString mGroup;
const nsCString mOrigin;
nsString mDirectoryPath;
/**
* Propagated from PrepareDatastoreOp. PrepareDatastoreOp may defer the
* creation of the localstorage client directory and database on the
* QuotaManager IO thread in its DatabaseWork method to
* Connection::EnsureStorageConnection, in which case the method needs to know
* it is responsible for taking those actions (without redundantly performing
* the existence checks).
*/
const bool mDatabaseWasNotAvailable;
bool mHasCreatedDatabase;
bool mFlushScheduled;
#ifdef DEBUG
bool mInUpdateBatch;
bool mFinished;
#endif
public:
NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); }
QuotaClient* GetQuotaClient() const {
MOZ_ASSERT(mQuotaClient);
return mQuotaClient;
}
ArchivedOriginScope* GetArchivedOriginScope() const {
return mArchivedOriginScope;
}
const nsCString& Origin() const { return mOrigin; }
const nsString& DirectoryPath() const { return mDirectoryPath; }
void GetFinishInfo(bool& aDatabaseWasNotAvailable,
bool& aHasCreatedDatabase) const {
AssertIsOnOwningThread();
MOZ_ASSERT(mFinished);
aDatabaseWasNotAvailable = mDatabaseWasNotAvailable;
aHasCreatedDatabase = mHasCreatedDatabase;
}
//////////////////////////////////////////////////////////////////////////////
// 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 SetItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta,
bool aIsNewItem);
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();
return mStorageConnection;
}
void CloseStorageConnection();
nsresult GetCachedStatement(const nsACString& aQuery,
CachedStatement* aCachedStatement);
nsresult BeginWriteTransaction();
nsresult CommitWriteTransaction();
nsresult RollbackWriteTransaction();
private:
// Only created by ConnectionThread.
Connection(ConnectionThread* aConnectionThread, const nsACString& aSuffix,
const nsACString& aGroup, const nsACString& aOrigin,
nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
bool aDatabaseWasNotAvailable);
~Connection();
void ScheduleFlush();
void Flush();
static void FlushTimerCallback(nsITimer* aTimer, void* aClosure);
};
class Connection::CachedStatement final {
friend class Connection;
nsCOMPtr<mozIStorageStatement> mStatement;
Maybe<mozStorageStatementScoper> 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<mozIStorageStatement> aStatement);
// No funny business allowed.
CachedStatement(const CachedStatement&) = delete;
CachedStatement& operator=(const CachedStatement&) = delete;
};
/**
* Helper to invoke EnsureStorageAndOriginIsInitialized on the QuotaManager IO
* thread from the LocalStorage connection thread when creating a database
* connection on demand. This is necessary because we attempt to defer the
* creation of the origin directory and the database until absolutely needed,
* but the directory creation and origin initialization must happen on the QM
* IO thread for invariant reasons. (We can't just use a mutex because there
* could be logic on the IO thread that also wants to deal with the same
* origin, so we need to queue a runnable and wait our turn.)
*/
class Connection::InitStorageAndOriginHelper final : public Runnable {
mozilla::Monitor mMonitor;
const nsCString mSuffix;
const nsCString mGroup;
const nsCString mOrigin;
nsString mOriginDirectoryPath;
nsresult mIOThreadResultCode;
bool mWaiting;
public:
InitStorageAndOriginHelper(const nsACString& aSuffix,
const nsACString& aGroup,
const nsACString& aOrigin)
: Runnable("dom::localstorage::Connection::InitStorageAndOriginHelper"),
mMonitor("InitStorageAndOriginHelper::mMonitor"),
mSuffix(aSuffix),
mGroup(aGroup),
mOrigin(aOrigin),
mIOThreadResultCode(NS_OK),
mWaiting(true) {
AssertIsOnConnectionThread();
}
nsresult BlockAndReturnOriginDirectoryPath(nsAString& aOriginDirectoryPath);
private:
~InitStorageAndOriginHelper() {}
nsresult RunOnIOThread();
NS_DECL_NSIRUNNABLE
};
class Connection::FlushOp final : public ConnectionDatastoreOperationBase {
ConnectionWriteOptimizer mWriteOptimizer;
bool mShadowWrites;
public:
FlushOp(Connection* aConnection, ConnectionWriteOptimizer&& aWriteOptimizer);
private:
nsresult DoDatastoreWork() override;
void Cleanup() override;
};
class Connection::CloseOp final : public ConnectionDatastoreOperationBase {
nsCOMPtr<nsIRunnable> mCallback;
public:
CloseOp(Connection* aConnection, nsIRunnable* aCallback)
: ConnectionDatastoreOperationBase(aConnection,
/* aEnsureStorageConnection */ false),
mCallback(aCallback) {}
private:
nsresult DoDatastoreWork() override;
void Cleanup() override;
};
class ConnectionThread final {
friend class Connection;
nsCOMPtr<nsIThread> mThread;
nsRefPtrHashtable<nsCStringHashKey, Connection> mConnections;
public:
ConnectionThread();
void AssertIsOnOwningThread() const {
NS_ASSERT_OWNINGTHREAD(ConnectionThread);
}
bool IsOnConnectionThread();
void AssertIsOnConnectionThread();
already_AddRefed<Connection> CreateConnection(
const nsACString& aSuffix, const nsACString& aGroup,
const nsACString& aOrigin,
nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
bool aDatabaseWasNotAvailable);
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
: public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
RefPtr<DirectoryLock> mDirectoryLock;
RefPtr<Connection> mConnection;
RefPtr<QuotaObject> mQuotaObject;
nsCOMPtr<nsIRunnable> mCompleteCallback;
/**
* PrepareDatastoreOps register themselves with the Datastore at
* and unregister in PrepareDatastoreOp::Cleanup.
*/
nsTHashtable<nsPtrHashKey<PrepareDatastoreOp>> 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<nsPtrHashKey<PreparedDatastore>> 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<nsPtrHashKey<Database>> 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<nsPtrHashKey<Database>> mActiveDatabases;
/**
* Non-authoritative hashtable representation of mOrderedItems for efficient
* lookup.
*/
nsDataHashtable<nsStringHashKey, LSValue> mValues;
/**
* The authoritative ordered state of the Datastore; mValue also exists as an
* unordered hashtable for efficient lookup.
*/
nsTArray<LSItemInfo> mOrderedItems;
nsTArray<int64_t> mPendingUsageDeltas;
DatastoreWriteOptimizer mWriteOptimizer;
const nsCString mGroup;
const nsCString mOrigin;
const uint32_t mPrivateBrowsingId;
int64_t mUsage;
int64_t mUpdateBatchUsage;
int64_t mSizeOfKeys;
int64_t mSizeOfItems;
bool mClosed;
bool mInUpdateBatch;
public:
// Created by PrepareDatastoreOp.
Datastore(const nsACString& aGroup, const nsACString& aOrigin,
uint32_t aPrivateBrowsingId, int64_t aUsage, int64_t aSizeOfKeys,
int64_t aSizeOfItems,
already_AddRefed<DirectoryLock>&& aDirectoryLock,
already_AddRefed<Connection>&& aConnection,
already_AddRefed<QuotaObject>&& aQuotaObject,
nsDataHashtable<nsStringHashKey, LSValue>& aValues,
nsTArray<LSItemInfo>& 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);
void NoteLiveDatabase(Database* aDatabase);
void NoteFinishedDatabase(Database* aDatabase);
void NoteActiveDatabase(Database* aDatabase);
void NoteInactiveDatabase(Database* aDatabase);
void GetSnapshotLoadInfo(const nsString& aKey, bool& aAddKeyToUnknownItems,
nsTHashtable<nsStringHashKey>& aLoadedItems,
nsTArray<LSItemInfo>& aItemInfos,
uint32_t& aNextLoadIndex,
LSSnapshot::LoadState& aLoadState);
uint32_t GetLength() const { return mValues.Count(); }
const nsTArray<LSItemInfo>& GetOrderedItems() const { return mOrderedItems; }
void GetItem(const nsString& aKey, LSValue& aValue) const;
void GetKeys(nsTArray<nsString>& 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& aKey,
const LSValue& aValue);
void RemoveItem(Database* aDatabase, const nsString& aKey);
void Clear(Database* aDatabase);
void BeginUpdateBatch(int64_t aSnapshotInitialUsage);
int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage);
int64_t GetUsage() const { return mUsage; }
int64_t RequestUpdateUsage(int64_t aRequestedSize, int64_t aMinSize);
bool HasOtherProcessObservers(Database* aDatabase);
void NotifyOtherProcessObservers(Database* aDatabase,
const nsString& aDocumentURI,
const nsString& aKey,
const LSValue& aOldValue,
const LSValue& aNewValue);
void NoteChangedObserverArray(const nsTArray<Observer*>& aObservers);
void Stringify(nsACString& aResult) const;
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 LSValue& aOldValue, bool aAffectsOrder);
};
class PreparedDatastore {
RefPtr<Datastore> mDatastore;
nsCOMPtr<nsITimer> mTimer;
const Maybe<ContentParentId> 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<ContentParentId>& 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<ContentParentId>& 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,
public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
RefPtr<Datastore> mDatastore;
Snapshot* mSnapshot;
const PrincipalInfo mPrincipalInfo;
const Maybe<ContentParentId> 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<ContentParentId>& 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();
void ForceKill();
void Stringify(nsACString& aResult) const;
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 nsString& aKey,
const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
mozilla::ipc::IPCResult RecvPBackgroundLSSnapshotConstructor(
PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI,
const nsString& aKey, 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<Database> mDatabase;
RefPtr<Datastore> 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<nsStringHashKey> mLoadedItems;
/**
* The set of keys for which a RecvLoadValueAndMoreItems 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<nsStringHashKey> 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<nsStringHashKey, LSValue> 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<nsString> mKeys;
nsString mDocumentURI;
/**
* The index used for restoring iteration over not yet sent key/value pairs to
* the child LSSnapshot.
*/
uint32_t mNextLoadIndex;
/**
* 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 RecvLoadValueAndMoreItems 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
* RecvLoadValueAndMoreItem 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;
bool mHasOtherProcessObservers;
public:
// Created in AllocPBackgroundLSSnapshotParent.
Snapshot(Database* aDatabase, const nsAString& aDocumentURI);
void Init(nsTHashtable<nsStringHashKey>& aLoadedItems,
nsTHashtable<nsStringHashKey>& aUnknownItems,
uint32_t aNextLoadIndex, uint32_t aTotalLength,
int64_t aInitialUsage, int64_t aPeakUsage,
LSSnapshot::LoadState aLoadState, bool aHasOtherProcessObservers) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aInitialUsage >= 0);
MOZ_ASSERT(aPeakUsage >= aInitialUsage);
MOZ_ASSERT_IF(aLoadState != LSSnapshot::LoadState::AllOrderedItems,
aNextLoadIndex < aTotalLength);
MOZ_ASSERT(mTotalLength == 0);
MOZ_ASSERT(mUsage == -1);
MOZ_ASSERT(mPeakUsage == -1);
mLoadedItems.SwapElements(aLoadedItems);
mUnknownItems.SwapElements(aUnknownItems);
mNextLoadIndex = aNextLoadIndex;
mTotalLength = aTotalLength;
mUsage = aInitialUsage;
mPeakUsage = aPeakUsage;
if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) {
MOZ_ASSERT(mUnknownItems.Count() == 0);
mLoadKeysReceived = true;
} else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) {
MOZ_ASSERT(mLoadedItems.Count() == 0);
MOZ_ASSERT(mUnknownItems.Count() == 0);
MOZ_ASSERT(mNextLoadIndex == mTotalLength);
mLoadedReceived = true;
mLoadedAllItems = true;
mLoadKeysReceived = true;
}
mHasOtherProcessObservers = aHasOtherProcessObservers;
}
/**
* 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 LSValue& aOldValue,
bool aAffectsOrder);
void MarkDirty();
bool IsDirty() const {
AssertIsOnBackgroundThread();
return mSentMarkDirty;
}
bool HasOtherProcessObservers() const {
AssertIsOnBackgroundThread();
return mHasOtherProcessObservers;
}
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<LSWriteInfo>&& aWriteInfos) override;
mozilla::ipc::IPCResult RecvCheckpointAndNotify(
nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) override;
mozilla::ipc::IPCResult RecvFinish() override;
mozilla::ipc::IPCResult RecvLoaded() override;
mozilla::ipc::IPCResult RecvLoadValueAndMoreItems(
const nsString& aKey, LSValue* aValue,
nsTArray<LSItemInfo>* aItemInfos) override;
mozilla::ipc::IPCResult RecvLoadKeys(nsTArray<nsString>* 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 LSValue& aOldValue,
const LSValue& 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 StartingRequest.
Initial,
// Waiting to start/starting request on the PBackground 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.
StartingRequest,
// 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<nsIEventTarget> mMainEventTarget;
const LSRequestParams mParams;
Maybe<ContentParentId> mContentParentId;
State mState;
bool mWaitingForFinish;
public:
LSRequestBase(nsIEventTarget* aMainEventTarget,
const LSRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId);
void Dispatch();
void StringifyState(nsACString& aResult) const;
virtual void Stringify(nsACString& aResult) const;
virtual void Log();
protected:
~LSRequestBase() override;
virtual nsresult Start() = 0;
virtual nsresult NestedRun();
virtual void GetResponse(LSRequestResponse& aResponse) = 0;
virtual void Cleanup() {}
private:
bool VerifyRequestParams();
nsresult StartRequest();
void SendReadyMessage();
nsresult SendReadyMessageInternal();
void Finish();
void FinishInternal();
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,
public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
class LoadDataOp;
class CompressFunction;
class CompressibleFunction;
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<nsIEventTarget> mMainEventTarget;
RefPtr<PrepareDatastoreOp> mDelayedOp;
RefPtr<DirectoryLock> mPendingDirectoryLock;
RefPtr<DirectoryLock> mDirectoryLock;
RefPtr<Connection> mConnection;
RefPtr<Datastore> mDatastore;
nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
LoadDataOp* mLoadDataOp;
nsDataHashtable<nsStringHashKey, LSValue> mValues;
nsTArray<LSItemInfo> mOrderedItems;
nsCString mSuffix;
nsCString mGroup;
nsCString mMainThreadOrigin;
nsCString mOrigin;
nsString mDatabaseFilePath;
uint32_t mPrivateBrowsingId;
int64_t mUsage;
int64_t mSizeOfKeys;
int64_t mSizeOfItems;
uint64_t mDatastoreId;
NestedState mNestedState;
const bool mForPreload;
bool mDatabaseNotAvailable;
bool mRequestedDirectoryLock;
bool mInvalidated;
#ifdef DEBUG
int64_t mDEBUGUsage;
#endif
public:
PrepareDatastoreOp(nsIEventTarget* aMainEventTarget,
const LSRequestParams& aParams,
const Maybe<ContentParentId>& 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;
}
void StringifyNestedState(nsACString& aResult) const;
void Stringify(nsACString& aResult) const override;
void Log() override;
private:
~PrepareDatastoreOp() override;
nsresult Start() 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<QuotaObject> 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<PrepareDatastoreOp> 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 PrepareDatastoreOp::CompressFunction final : public mozIStorageFunction {
private:
~CompressFunction() = default;
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
};
class PrepareDatastoreOp::CompressibleFunction final
: public mozIStorageFunction {
private:
~CompressibleFunction() = default;
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
};
class PrepareObserverOp : public LSRequestBase {
nsCString mOrigin;
public:
PrepareObserverOp(nsIEventTarget* aMainEventTarget,
const LSRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId);
private:
nsresult Start() 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 StartingRequest.
Initial,
// Waiting to start/starting request on the PBackground thread. Next step is
// SendingResults.
StartingRequest,
// Waiting to send/sending results on the PBackground thread. Next step is
// Completed.
SendingResults,
// All done.
Completed
};
const LSSimpleRequestParams mParams;
Maybe<ContentParentId> mContentParentId;
State mState;
public:
LSSimpleRequestBase(const LSSimpleRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId);
void Dispatch();
protected:
~LSSimpleRequestBase() override;
virtual nsresult Start() = 0;
virtual void GetResponse(LSSimpleRequestResponse& aResponse) = 0;
private:
bool VerifyRequestParams();
nsresult StartRequest();
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 {
nsCString mOrigin;
public:
PreloadedOp(const LSSimpleRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId);
private:
nsresult Start() 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<OriginAttributesPattern> mPattern;
explicit Pattern(const OriginAttributesPattern& aPattern)
: mPattern(MakeUnique<OriginAttributesPattern>(aPattern)) {}
Pattern(const Pattern& aOther)
: mPattern(MakeUnique<OriginAttributesPattern>(*aOther.mPattern)) {}
Pattern(Pattern&& aOther) = default;
const OriginAttributesPattern& GetPattern() const {
MOZ_ASSERT(mPattern);
return *mPattern;
}
};
struct Null {};
using DataType = Variant<Origin, Pattern, Prefix, Null>;
DataType mData;
public:
static ArchivedOriginScope* CreateFromOrigin(
const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey);
static ArchivedOriginScope* CreateFromPrefix(const nsACString& aOriginKey);
static ArchivedOriginScope* CreateFromPattern(
const OriginAttributesPattern& aPattern);
static ArchivedOriginScope* CreateFromNull();
bool IsOrigin() const { return mData.is<Origin>(); }
bool IsPrefix() const { return mData.is<Prefix>(); }
bool IsPattern() const { return mData.is<Pattern>(); }
bool IsNull() const { return mData.is<Null>(); }
const nsACString& OriginSuffix() const {
MOZ_ASSERT(IsOrigin());
return mData.as<Origin>().OriginSuffix();
}
const nsACString& OriginNoSuffix() const {
MOZ_ASSERT(IsOrigin() || IsPrefix());
if (IsOrigin()) {
return mData.as<Origin>().OriginNoSuffix();
}
return mData.as<Prefix>().OriginNoSuffix();
}
const OriginAttributesPattern& GetPattern() const {
MOZ_ASSERT(IsPattern());
return mData.as<Pattern>().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 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,
bool aForGetUsage) override;
nsresult GetUsageForOrigin(PersistenceType aPersistenceType,
const nsACString& aGroup,
const nsACString& aOrigin,
const AtomicBool& aCanceled,
UsageInfo* aUsageInfo) override;
nsresult AboutToClearOrigins(
const Nullable<PersistenceType>& 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;
void ShutdownTimedOut();
nsresult CreateArchivedOriginScope(
const OriginScope& aOriginScope,
nsAutoPtr<ArchivedOriginScope>& 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
};
/*******************************************************************************
* Helper classes
******************************************************************************/
class MOZ_STACK_CLASS AutoWriteTransaction final {
Connection* mConnection;
Maybe<MutexAutoLock> mShadowDatabaseLock;
bool mShadowWrites;
public:
explicit AutoWriteTransaction(bool aShadowWrites);
~AutoWriteTransaction();
nsresult Start(Connection* aConnection);
nsresult Commit();
private:
nsresult LockAndAttachShadowDatabase(Connection* aConnection);
nsresult DetachShadowDatabaseAndUnlock();
};
/*******************************************************************************
* Globals
******************************************************************************/
#ifdef DEBUG
bool gLocalStorageInitialized = false;
#endif
typedef nsTArray<CheckedUnsafePtr<PrepareDatastoreOp>> PrepareDatastoreOpArray;
StaticAutoPtr<PrepareDatastoreOpArray> gPrepareDatastoreOps;
// nsCStringHashKey with disabled memmove
class nsCStringHashKeyDM : public nsCStringHashKey {
public:
explicit nsCStringHashKeyDM(const nsCStringHashKey::KeyTypePointer aKey)
: nsCStringHashKey(aKey) {}
enum { ALLOW_MEMMOVE = false };
};
// When CheckedUnsafePtr's checking is enabled, it's necessary to ensure that
// the hashtable uses the copy constructor instead of memmove for moving entries
// since memmove will break CheckedUnsafePtr in a memory-corrupting way.
typedef std::conditional<DiagnosticAssertEnabled::value, nsCStringHashKeyDM,
nsCStringHashKey>::type DatastoreHashKey;
typedef nsDataHashtable<DatastoreHashKey, CheckedUnsafePtr<Datastore>>
DatastoreHashtable;
StaticAutoPtr<DatastoreHashtable> gDatastores;
uint64_t gLastDatastoreId = 0;
typedef nsClassHashtable<nsUint64HashKey, PreparedDatastore>
PreparedDatastoreHashtable;
StaticAutoPtr<PreparedDatastoreHashtable> gPreparedDatastores;
typedef nsTArray<CheckedUnsafePtr<Database>> LiveDatabaseArray;
StaticAutoPtr<LiveDatabaseArray> gLiveDatabases;
StaticRefPtr<ConnectionThread> gConnectionThread;
uint64_t gLastObserverId = 0;
typedef nsRefPtrHashtable<nsUint64HashKey, Observer> PreparedObserverHashtable;
StaticAutoPtr<PreparedObserverHashtable> gPreparedObsevers;
typedef nsClassHashtable<nsCStringHashKey, nsTArray<Observer*>>
ObserverHashtable;
StaticAutoPtr<ObserverHashtable> gObservers;
Atomic<bool> gNextGen(kDefaultNextGen);
Atomic<bool> gShadowWrites(kDefaultShadowWrites);
Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill);
Atomic<int32_t, Relaxed> gSnapshotGradualPrefill(
kDefaultSnapshotGradualPrefill);
Atomic<bool> gClientValidation(kDefaultClientValidation);
typedef nsDataHashtable<nsCStringHashKey, int64_t> UsageHashtable;
StaticAutoPtr<ArchivedOriginHashtable> 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();
}
already_AddRefed<Datastore> GetDatastore(const nsACString& aOrigin) {
AssertIsOnBackgroundThread();
if (gDatastores) {
CheckedUnsafePtr<Datastore> datastore;
if (gDatastores->Get(aOrigin, &datastore)) {
MOZ_ASSERT(datastore);
RefPtr<Datastore> result(datastore);
return result.forget();
}
}
return nullptr;
}
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<mozIStorageConnection> 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<mozIStorageStatement> 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<ArchivedOriginHashtable> 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> 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<mozIStorageStatement> 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;
}
void SnapshotGradualPrefillPrefChangedCallback(const char* aPrefName,
void* aClosure) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!strcmp(aPrefName, kSnapshotGradualPrefillPref));
MOZ_ASSERT(!aClosure);
int32_t snapshotGradualPrefill =
Preferences::GetInt(aPrefName, kDefaultSnapshotGradualPrefill);
// The magic -1 is for use only by tests.
if (snapshotGradualPrefill == -1) {
snapshotGradualPrefill = INT32_MAX;
}
gSnapshotGradualPrefill = snapshotGradualPrefill;
}
void ClientValidationPrefChangedCallback(const char* aPrefName,
void* aClosure) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!strcmp(aPrefName, kClientValidationPref));
MOZ_ASSERT(!aClosure);
gClientValidation = Preferences::GetBool(aPrefName, kDefaultClientValidation);
}
template <typename P>
void CollectDatabases(P aPredicate, nsTArray<RefPtr<Database>>& aDatabases) {
AssertIsOnBackgroundThread();
if (!gLiveDatabases) {
return;
}
for (const auto& database : *gLiveDatabases) {
if (aPredicate(database)) {
aDatabases.AppendElement(database);
}
}
}
template <typename P>
void RequestAllowToCloseIf(P aPredicate) {
AssertIsOnBackgroundThread();
nsTArray<RefPtr<Database>> databases;
CollectDatabases(aPredicate, databases);
for (const auto& database : databases) {
MOZ_ASSERT(database);
database->RequestAllowToClose();
}
databases.Clear();
}
void ForceKillDatabases() {
AssertIsOnBackgroundThread();
nsTArray<RefPtr<Database>> databases;
CollectDatabases([](const Database* const) { return true; }, databases);
for (const auto& database : databases) {
MOZ_ASSERT(database);
database->ForceKill();
}
databases.Clear();
}
bool VerifyPrincipalInfo(const PrincipalInfo& aPrincipalInfo,
const PrincipalInfo& aStoragePrincipalInfo) {
AssertIsOnBackgroundThread();
if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
return false;
}
if (NS_WARN_IF(!StoragePrincipalHelper::
VerifyValidStoragePrincipalInfoForPrincipalInfo(
aStoragePrincipalInfo, aPrincipalInfo))) {
return false;
}
return true;
}
bool VerifyClientId(const Maybe<ContentParentId>& aContentParentId,
const PrincipalInfo& aPrincipalInfo,
const Maybe<nsID>& aClientId) {
AssertIsOnBackgroundThread();
if (gClientValidation) {
if (NS_WARN_IF(aClientId.isNothing())) {
return false;
}
RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance();
if (svc && NS_WARN_IF(!svc->HasWindow(aContentParentId, aPrincipalInfo,
aClientId.ref()))) {
return false;
}
}
return true;
}
bool VerifyOriginKey(const nsACString& aOriginKey,
const PrincipalInfo& aPrincipalInfo) {
AssertIsOnBackgroundThread();
nsCString originAttrSuffix;
nsCString originKey;
nsresult rv = GenerateOriginKey2(aPrincipalInfo, originAttrSuffix, originKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (NS_WARN_IF(originKey != aOriginKey)) {
LS_WARNING("originKey (%s) doesn't match passed one (%s)!", originKey.get(),
nsCString(aOriginKey).get());
return false;
}
return true;
}
} // 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<mozIStorageService> 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!");
}
Preferences::AddAtomicBoolVarCache(&gNextGen, kNextGenPref, kDefaultNextGen);
Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback,
kShadowWritesPref);
Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback,
kSnapshotPrefillPref);
Preferences::RegisterCallbackAndCall(
SnapshotGradualPrefillPrefChangedCallback, kSnapshotGradualPrefillPref);
Preferences::RegisterCallbackAndCall(ClientValidationPrefChangedCallback,
kClientValidationPref);
#ifdef DEBUG
gLocalStorageInitialized = true;
#endif
}
bool GetCurrentNextGenPrefValue() { return gNextGen; }
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> 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> preparedDatastore;
gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore);
MOZ_ASSERT(preparedDatastore);
auto* database = static_cast<Database*>(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<Database> actor = dont_AddRef(static_cast<Database*>(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> 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> observer;
gPreparedObsevers->Remove(aObserverId, observer.StartAssignment());
MOZ_ASSERT(observer);
if (!gPreparedObsevers->Count()) {
gPreparedObsevers = nullptr;
}
if (!gObservers) {
gObservers = new ObserverHashtable();
}
nsTArray<Observer*>* array;
if (!gObservers->Get(observer->Origin(), &array)) {
array = new nsTArray<Observer*>();
gObservers->Put(observer->Origin(), array);
}
array->AppendElement(observer);
if (RefPtr<Datastore> datastore = GetDatastore(observer->Origin())) {
datastore->NoteChangedObserverArray(*array);
}
return true;
}
bool DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
// Transfer ownership back from IPDL.
RefPtr<Observer> actor = dont_AddRef(static_cast<Observer*>(aActor));
return true;
}
PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent(
PBackgroundParent* aBackgroundActor, const LSRequestParams& aParams) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
return nullptr;
}
Maybe<ContentParentId> contentParentId;
uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
if (childID) {
contentParentId = Some(ContentParentId(childID));
}
// If we're in the same process as the actor, we need to get the target event
// queue from the current RequestHelper.
nsCOMPtr<nsIEventTarget> mainEventTarget;
if (!BackgroundParent::IsOtherProcessActor(aBackgroundActor)) {
mainEventTarget = LSObject::GetSyncLoopEventTarget();
}
RefPtr<LSRequestBase> actor;
switch (aParams.type()) {
case LSRequestParams::TLSRequestPreloadDatastoreParams:
case LSRequestParams::TLSRequestPrepareDatastoreParams: {
RefPtr<PrepareDatastoreOp> prepareDatastoreOp =
new PrepareDatastoreOp(mainEventTarget, aParams, contentParentId);
if (!gPrepareDatastoreOps) {
gPrepareDatastoreOps = new PrepareDatastoreOpArray();
}
gPrepareDatastoreOps->AppendElement(prepareDatastoreOp);
actor = std::move(prepareDatastoreOp);
break;
}
case LSRequestParams::TLSRequestPrepareObserverParams: {
RefPtr<PrepareObserverOp> prepareObserverOp =
new PrepareObserverOp(mainEventTarget, aParams, contentParentId);
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<LSRequestBase*>(aActor);
op->Dispatch();
return true;
}
bool DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor) {
AssertIsOnBackgroundThread();
// Transfer ownership back from IPDL.
RefPtr<LSRequestBase> actor =
dont_AddRef(static_cast<LSRequestBase*>(aActor));
return true;
}
PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent(
PBackgroundParent* aBackgroundActor, const LSSimpleRequestParams& aParams) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
return nullptr;
}
Maybe<ContentParentId> contentParentId;
uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
if (childID) {
contentParentId = Some(ContentParentId(childID));
}
RefPtr<LSSimpleRequestBase> actor;
switch (aParams.type()) {
case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
RefPtr<PreloadedOp> preloadedOp =
new PreloadedOp(aParams, contentParentId);
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<LSSimpleRequestBase*>(aActor);
op->Dispatch();
return true;
}
bool DeallocPBackgroundLSSimpleRequestParent(
PBackgroundLSSimpleRequestParent* aActor) {
AssertIsOnBackgroundThread();
// Transfer ownership back from IPDL.
RefPtr<LSSimpleRequestBase> actor =
dont_AddRef(static_cast<LSSimpleRequestBase*>(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->Clear(nullptr);
}
}
}
return true;
}
namespace localstorage {
already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
RefPtr<QuotaClient> client = new QuotaClient();
return client.forget();
}
} // namespace localstorage
/*******************************************************************************
* DatastoreWriteOptimizer
******************************************************************************/
void DatastoreWriteOptimizer::ApplyAndReset(
nsTArray<LSItemInfo>& aOrderedItems) {
AssertIsOnOwningThread();
// The mWriteInfos hash table contains all write infos, but it keeps them in
// an arbitrary order, which means write infos need to be sorted before being
// processed. However, the order is not important for deletions and normal
// updates. Usually, filtering out deletions and updates would require extra
// work, but we have to check the hash table for each ordered item anyway, so
// we can remove the write info if it is a deletion or update without adding
// extra overhead. In the end, only insertions need to be sorted before being
// processed.
if (mTruncateInfo) {
aOrderedItems.Clear();
mTruncateInfo = 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::DeleteItem:
aOrderedItems.RemoveElementAt(index);
entry.Remove();
break;
case WriteInfo::UpdateItem: {
auto updateItemInfo = static_cast<UpdateItemInfo*>(writeInfo);
if (updateItemInfo->UpdateWithMove()) {
// See the comment in LSWriteOptimizer::InsertItem for more details
// about the UpdateWithMove flag.
aOrderedItems.RemoveElementAt(index);
entry.Data() = new InsertItemInfo(updateItemInfo->SerialNumber(),
updateItemInfo->GetKey(),
updateItemInfo->GetValue());
} else {
item.value() = updateItemInfo->GetValue();
entry.Remove();
}
break;
}
case WriteInfo::InsertItem:
break;
default:
MOZ_CRASH("Bad type!");
}
}
}
nsTArray<WriteInfo*> writeInfos;
GetSortedWriteInfos(writeInfos);
for (WriteInfo* writeInfo : writeInfos) {
MOZ_ASSERT(writeInfo->GetType() == WriteInfo::InsertItem);
auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo);
LSItemInfo* itemInfo = aOrderedItems.AppendElement();
itemInfo->key() = insertItemInfo->GetKey();
itemInfo->value() = insertItemInfo->GetValue();
}
mWriteInfos.Clear();
}
/*******************************************************************************
* ConnectionWriteOptimizer
******************************************************************************/
nsresult ConnectionWriteOptimizer::Perform(Connection* aConnection,
bool aShadowWrites,
int64_t& aOutUsage) {
AssertIsOnConnectionThread();
MOZ_ASSERT(aConnection);
// The order of elements is not stored in the database, so write infos don't
// need to be sorted before being processed.
nsresult rv;
if (mTruncateInfo) {
rv = PerformTruncate(aConnection, aShadowWrites);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
for (auto iter = mWriteInfos.ConstIter(); !iter.Done(); iter.Next()) {
WriteInfo* writeInfo = iter.Data();
switch (writeInfo->GetType()) {
case WriteInfo::InsertItem:
case WriteInfo::UpdateItem: {
auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo);
rv = PerformInsertOrUpdate(aConnection, aShadowWrites,
insertItemInfo->GetKey(),
insertItemInfo->GetValue());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
break;
}
case WriteInfo::DeleteItem: {
auto deleteItemInfo = static_cast<DeleteItemInfo*>(writeInfo);
rv =
PerformDelete(aConnection, aShadowWrites, deleteItemInfo->GetKey());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
break;
}
default:
MOZ_CRASH("Bad type!");
}
}
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 ConnectionWriteOptimizer::PerformInsertOrUpdate(
Connection* aConnection, bool aShadowWrites, const nsAString& aKey,
const LSValue& aValue) {
AssertIsOnConnectionThread();
MOZ_ASSERT(aConnection);
Connection::CachedStatement stmt;
nsresult rv = aConnection->GetCachedStatement(
NS_LITERAL_CSTRING(
"INSERT OR REPLACE INTO data (key, value, utf16Length, compressed) "
"VALUES(:key, :value, :utf16Length, :compressed)"),
&stmt);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), aKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), aValue);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("utf16Length"),
aValue.UTF16Length());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("compressed"),
aValue.IsCompressed());
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"), aKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (aValue.IsCompressed()) {
nsCString value;
if (NS_WARN_IF(!SnappyUncompress(aValue, value))) {
return NS_ERROR_FAILURE;
}
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), value);
} else {
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), aValue);
}
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 ConnectionWriteOptimizer::PerformDelete(Connection* aConnection,
bool aShadowWrites,
const nsAString& aKey) {
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"), aKey);
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"), aKey);
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 ConnectionWriteOptimizer::PerformTruncate(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, bool aEnsureStorageConnection)
: mConnection(aConnection),
mEnsureStorageConnection(aEnsureStorageConnection) {
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 = NS_OK;
// The boolean flag is only used by the CloseOp to avoid creating empty
// databases.
if (mEnsureStorageConnection) {
rv = mConnection->EnsureStorageConnection();
if (NS_WARN_IF(NS_FAILED(rv))) {
SetFailureCode(rv);
} else {
MOZ_ASSERT(mConnection->StorageConnection());
}
}
if (NS_SUCCEEDED(rv)) {
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);
}
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& aSuffix, const nsACString& aGroup,
const nsACString& aOrigin,
nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
bool aDatabaseWasNotAvailable)
: mConnectionThread(aConnectionThread),
mQuotaClient(QuotaClient::GetInstance()),
mArchivedOriginScope(std::move(aArchivedOriginScope)),
mSuffix(aSuffix),
mGroup(aGroup),
mOrigin(aOrigin),
mDatabaseWasNotAvailable(aDatabaseWasNotAvailable),
mHasCreatedDatabase(false),
mFlushScheduled(false)
#ifdef DEBUG
,
mInUpdateBatch(false),
mFinished(false)
#endif
{
AssertIsOnOwningThread();
MOZ_ASSERT(!aGroup.IsEmpty());
MOZ_ASSERT(!aOrigin.IsEmpty());
}
Connection::~Connection() {
AssertIsOnOwningThread();
MOZ_ASSERT(!mStorageConnection);
MOZ_ASSERT(!mCachedStatements.Count());
MOZ_ASSERT(!mFlushScheduled);
MOZ_ASSERT(!mInUpdateBatch);
MOZ_ASSERT(mFinished);
}
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<CloseOp> op = new CloseOp(this, aCallback);
Dispatch(op);
}
void Connection::SetItem(const nsString& aKey, const LSValue& aValue,
int64_t aDelta, bool aIsNewItem) {
AssertIsOnOwningThread();
MOZ_ASSERT(mInUpdateBatch);
if (aIsNewItem) {
mWriteOptimizer.InsertItem(aKey, aValue, aDelta);
} else {
mWriteOptimizer.UpdateItem(aKey, aValue, aDelta);
}
}
void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) {
AssertIsOnOwningThread();
MOZ_ASSERT(mInUpdateBatch);
mWriteOptimizer.DeleteItem(aKey, aDelta);
}
void Connection::Clear(int64_t aDelta) {
AssertIsOnOwningThread();
MOZ_ASSERT(mInUpdateBatch);
mWriteOptimizer.Truncate(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) {
return NS_OK;
}
nsresult rv;
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
if (!mDatabaseWasNotAvailable || mHasCreatedDatabase) {
nsCOMPtr<nsIFile> directoryEntry;
rv = quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_DEFAULT, mOrigin,
getter_AddRefs(directoryEntry));
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 = 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;
}
nsString databaseFilePath;
rv = directoryEntry->GetPath(databaseFilePath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> storageConnection;
rv = GetStorageConnection(databaseFilePath,
getter_AddRefs(storageConnection));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mStorageConnection = storageConnection;
return NS_OK;
}
RefPtr<InitStorageAndOriginHelper> helper =
new InitStorageAndOriginHelper(mSuffix, mGroup, mOrigin);
nsString originDirectoryPath;
rv = helper->BlockAndReturnOriginDirectoryPath(originDirectoryPath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIFile> directoryEntry;
rv = NS_NewLocalFile(originDirectoryPath, false,
getter_AddRefs(directoryEntry));
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 = directoryEntry->GetPath(mDirectoryPath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool exists;
rv = directoryEntry->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!exists) {
rv = directoryEntry->Create(nsIFile::DIRECTORY_TYPE, 0755);
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;
}
#ifdef DEBUG
rv = directoryEntry->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(!exists);
#endif
nsCOMPtr<nsIFile> usageFile;
rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> storageConnection;
bool removedUsageFile;
auto autoRemove = MakeScopeExit([&] {
if (storageConnection) {
MOZ_ALWAYS_SUCCEEDS(storageConnection->Close());
}
nsresult rv = directoryEntry->Remove(false);
if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
NS_WARNING("Failed to remove database file!");
}
});
rv = CreateStorageConnection(directoryEntry, usageFile, mOrigin,
getter_AddRefs(storageConnection),
&removedUsageFile);
MOZ_ASSERT(!removedUsageFile);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(mQuotaClient);
MutexAutoLock shadowDatabaseLock(mQuotaClient->ShadowDatabaseMutex());
nsCOMPtr<mozIStorageConnection> shadowConnection;
if (!gInitializedShadowStorage) {
rv = CreateShadowStorageConnection(quotaManager->GetBasePath(),
getter_AddRefs(shadowConnection));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
gInitializedShadowStorage = true;
}
autoRemove.release();
if (!mHasCreatedDatabase) {
mHasCreatedDatabase = true;
}
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<mozIStorageStatement> 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;
}
nsresult Connection::BeginWriteTransaction() {
AssertIsOnConnectionThread();
MOZ_ASSERT(mStorageConnection);
CachedStatement stmt;
nsresult rv =
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;
}
return NS_OK;
}
nsresult Connection::CommitWriteTransaction() {
AssertIsOnConnectionThread();
MOZ_ASSERT(mStorageConnection);
CachedStatement stmt;
nsresult rv = 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;
}
return NS_OK;
}
nsresult Connection::RollbackWriteTransaction() {
AssertIsOnConnectionThread();
MOZ_ASSERT(mStorageConnection);
CachedStatement stmt;
nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &stmt);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// This may fail if SQLite already rolled back the transaction so ignore any
// errors.
Unused << stmt->Execute();
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<FlushOp> 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<Connection*>(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<mozIStorageStatement> aStatement) {
AssertIsOnConnectionThread();
mScoper.reset();
mStatement = aStatement;
if (mStatement) {
mScoper.emplace(mStatement);
}
}
nsresult
Connection::InitStorageAndOriginHelper::BlockAndReturnOriginDirectoryPath(
nsAString& aOriginDirectoryPath) {
AssertIsOnConnectionThread();
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
MOZ_ALWAYS_SUCCEEDS(
quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
mozilla::MonitorAutoLock lock(mMonitor);
while (mWaiting) {
lock.Wait();
}
if (NS_WARN_IF(NS_FAILED(mIOThreadResultCode))) {
return mIOThreadResultCode;
}
aOriginDirectoryPath = mOriginDirectoryPath;
return NS_OK;
}
nsresult Connection::InitStorageAndOriginHelper::RunOnIOThread() {
AssertIsOnIOThread();
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
nsCOMPtr<nsIFile> directoryEntry;
nsresult rv = quotaManager->EnsureStorageAndOriginIsInitialized(
PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin,
mozilla::dom::quota::Client::LS, getter_AddRefs(directoryEntry));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = directoryEntry->GetPath(mOriginDirectoryPath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
Connection::InitStorageAndOriginHelper::Run() {
AssertIsOnIOThread();
nsresult rv = RunOnIOThread();
if (NS_WARN_IF(NS_FAILED(rv))) {
mIOThreadResultCode = rv;
}
mozilla::MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mWaiting);
mWaiting = false;
lock.Notify();
return NS_OK;
}
Connection::FlushOp::FlushOp(Connection* aConnection,
ConnectionWriteOptimizer&& aWriteOptimizer)
: ConnectionDatastoreOperationBase(aConnection),
mWriteOptimizer(std::move(aWriteOptimizer)),
mShadowWrites(gShadowWrites) {}
nsresult Connection::FlushOp::DoDatastoreWork() {
AssertIsOnConnectionThread();
MOZ_ASSERT(mConnection);
AutoWriteTransaction autoWriteTransaction(mShadowWrites);
nsresult rv = autoWriteTransaction.Start(mConnection);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
int64_t usage;
rv = mWriteOptimizer.Perform(mConnection, mShadowWrites, usage);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIFile> usageFile;
rv = GetUsageFile(mConnection->DirectoryPath(), getter_AddRefs(usageFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIFile> 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 = autoWriteTransaction.Commit();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = usageJournalFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void Connection::FlushOp::Cleanup() {
AssertIsOnOwningThread();
mWriteOptimizer.Reset();
MOZ_ASSERT(!mWriteOptimizer.HasWrites());
ConnectionDatastoreOperationBase::Cleanup();
}
nsresult Connection::CloseOp::DoDatastoreWork() {
AssertIsOnConnectionThread();
MOZ_ASSERT(mConnection);
if (mConnection->StorageConnection()) {
mConnection->CloseStorageConnection();
}
return NS_OK;
}
void Connection::CloseOp::Cleanup() {
AssertIsOnOwningThread();
MOZ_ASSERT(mConnection);
mConnection->mConnectionThread->mConnections.Remove(mConnection->mOrigin);
#ifdef DEBUG
MOZ_ASSERT(!mConnection->mFinished);
mConnection->mFinished = true;
#endif
nsCOMPtr<nsIRunnable> 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(&current)) && current;
}
void ConnectionThread::AssertIsOnConnectionThread() {
MOZ_ASSERT(IsOnConnectionThread());
}
already_AddRefed<Connection> ConnectionThread::CreateConnection(
const nsACString& aSuffix, const nsACString& aGroup,
const nsACString& aOrigin,
nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
bool aDatabaseWasNotAvailable) {
AssertIsOnOwningThread();
MOZ_ASSERT(!aOrigin.IsEmpty());
MOZ_ASSERT(!mConnections.GetWeak(aOrigin));
RefPtr<Connection> connection =
new Connection(this, aSuffix, aGroup, aOrigin,
std::move(aArchivedOriginScope), aDatabaseWasNotAvailable);
mConnections.Put(aOrigin, connection);
return connection.forget();
}
void ConnectionThread::Shutdown() {
AssertIsOnOwningThread();
MOZ_ASSERT(mThread);
mThread->Shutdown();
}
/*******************************************************************************
* Datastore
******************************************************************************/
Datastore::Datastore(const nsACString& aGroup, const nsACString& aOrigin,
uint32_t aPrivateBrowsingId, int64_t aUsage,
int64_t aSizeOfKeys, int64_t aSizeOfItems,
already_AddRefed<DirectoryLock>&& aDirectoryLock,
already_AddRefed<Connection>&& aConnection,
already_AddRefed<QuotaObject>&& aQuotaObject,
nsDataHashtable<nsStringHashKey, LSValue>& aValues,
nsTArray<LSItemInfo>& aOrderedItems)
: mDirectoryLock(std::move(aDirectoryLock)),
mConnection(std::move(aConnection)),
mQuotaObject(std::move(aQuotaObject)),
mGroup(aGroup),
mOrigin(aOrigin),
mPrivateBrowsingId(aPrivateBrowsingId),
mUsage(aUsage),
mUpdateBatchUsage(-1),
mSizeOfKeys(aSizeOfKeys),
mSizeOfItems(aSizeOfItems),
mClosed(false),
mInUpdateBatch(false) {
AssertIsOnBackgroundThread();
mValues.SwapElements(aValues);
mOrderedItems.SwapElements(aOrderedItems);
}
Datastore::~Datastore() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mClosed);
}
void Datastore::Close() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mClosed);
MOZ_ASSERT(!mPrepareDatastoreOps.Count());
MOZ_ASSERT(!mPreparedDatastores.Count());
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<nsIRunnable> 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();
}
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();
}
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<bool> ok = UpdateUsage(finalDelta);
MOZ_ASSERT(ok);
}
mPendingUsageDeltas.Clear();
}
}
void Datastore::GetSnapshotLoadInfo(const nsString& aKey,
bool& aAddKeyToUnknownItems,
nsTHashtable<nsStringHashKey>& aLoadedItems,
nsTArray<LSItemInfo>& aItemInfos,
uint32_t& aNextLoadIndex,
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<int64_t>(item.key().Length());
sizeOfKeys += sizeOfKey;
sizeOfItems += sizeOfKey + static_cast<int64_t>(item.value().Length());
}
MOZ_ASSERT(mSizeOfKeys == sizeOfKeys);
MOZ_ASSERT(mSizeOfItems == sizeOfItems);
#endif
// Computes load state optimized for current size of keys and items.
// Zero key length and value can be passed to do a quick initial estimation.
// If computed load state is already AllOrderedItems then excluded key length
// and value length can't make it any better.
auto GetLoadState = [&](auto aKeyLength, auto aValueLength) {
if (mSizeOfKeys - aKeyLength <= gSnapshotPrefill) {
if (mSizeOfItems - aKeyLength - aValueLength <= gSnapshotPrefill) {
return LSSnapshot::LoadState::AllOrderedItems;
}
return LSSnapshot::LoadState::AllOrderedKeys;
}
return LSSnapshot::LoadState::Partial;
};
// Value for given aKey if aKey is not void (can be void too if value doesn't
// exist for given aKey).
LSValue value;
// If aKey and value are not void, checkKey will be set to true. Once we find
// an item for given aKey in one of the loops below, checkKey is set to false
// to prevent additional comparison of strings (string implementation compares
// string lengths first to avoid char by char comparison if possible).
bool checkKey = false;
// Avoid additional hash lookup if all ordered items fit into initial prefill
// already.
LSSnapshot::LoadState loadState = GetLoadState(/* aKeyLength */ 0,
/* aValueLength */ 0);
if (loadState != LSSnapshot::LoadState::AllOrderedItems && !aKey.IsVoid()) {
GetItem(aKey, value);
if (!value.IsVoid()) {
// Ok, we have a non void aKey and value.
// We have to watch for aKey during one of the loops below to exclude it
// from the size computation. The super fast mode (AllOrderedItems)
// doesn't have to do that though.
checkKey = true;
// We have to compute load state again because aKey length and value
// length is excluded from the size in this case.
loadState = GetLoadState(aKey.Length(), value.Length());
}
}
switch (loadState) {
case LSSnapshot::LoadState::AllOrderedItems: {
// We're sending all ordered items, we don't need to check keys because
// mOrderedItems must contain a value for aKey if checkKey is true.
aItemInfos.AppendElements(mOrderedItems);
MOZ_ASSERT(aItemInfos.Length() == mValues.Count());
aNextLoadIndex = mValues.Count();
aAddKeyToUnknownItems = false;
break;
}
case LSSnapshot::LoadState::AllOrderedKeys: {
// We don't have enough snapshot budget to send all items, but we do have
// enough to send all of the keys and to make a best effort to populate as
// many values as possible. We send void string values once we run out of
// budget. A complicating factor is that we want to make sure that we send
// the value for aKey which is a localStorage read that's triggering this
// request. Since that key can happen anywhere in the list of items, we
// need to handle it specially.
//
// The loop is effectively doing 2 things in parallel:
//
// 1. Looking for the `aKey` to send. This is tracked by `checkKey`
// which is true if there was an `aKey` specified and until we
// populate its value, and false thereafter.
// 2. Sending values until we run out of `size` budget and switch to
// sending void values. `doneSendingValues` tracks when we've run out
// of size budget, with `setVoidValue` tracking whether a value
// should be sent for each turn of the event loop but can be
// overridden when `aKey` is found.
int64_t size = mSizeOfKeys;
bool setVoidValue = false;
bool doneSendingValues = false;
for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
const LSItemInfo& item = mOrderedItems[index];
const nsString& key = item.key();
const LSValue& value = item.value();
if (checkKey && key == aKey) {
checkKey = false;
setVoidValue = false;
} else if (!setVoidValue) {
if (doneSendingValues) {
setVoidValue = true;
} else {
size += static_cast<int64_t>(value.Length());
if (size > gSnapshotPrefill) {
setVoidValue = true;
doneSendingValues = true;
// We set doneSendingValues to true and that will guard against
// entering this branch during next iterations. So aNextLoadIndex
// is set only once.
aNextLoadIndex = index;
}
}
}
LSItemInfo* itemInfo = aItemInfos.AppendElement();
itemInfo->key() = key;
if (setVoidValue) {
itemInfo->value().SetIsVoid(true);
} else {
aLoadedItems.PutEntry(key);
itemInfo->value() = value;
}
}
aAddKeyToUnknownItems = false;
break;
}
case LSSnapshot::LoadState::Partial: {
int64_t size = 0;
for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
const LSItemInfo& item = mOrderedItems[index];
const nsString& key = item.key();
const LSValue& value = item.value();
if (checkKey && key == aKey) {
checkKey = false;
} else {
size += static_cast<int64_t>(key.Length()) +
static_cast<int64_t>(value.Length());
if (size > gSnapshotPrefill) {
aNextLoadIndex = index;
break;
}
}
aLoadedItems.PutEntry(key);
LSItemInfo* itemInfo = aItemInfos.AppendElement();
itemInfo->key() = key;
itemInfo->value() = value;
}
aAddKeyToUnknownItems = false;
if (!aKey.IsVoid()) {
if (value.IsVoid()) {
aAddKeyToUnknownItems = true;
} else if (checkKey) {
// The item wasn't added in the loop above, add it here.
LSItemInfo* itemInfo = aItemInfos.AppendElement();
itemInfo->key() = aKey;
itemInfo->value() = value;
}
}
MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
break;
}
default:
MOZ_CRASH("Bad load state value!");
}
aLoadState = loadState;
}
void Datastore::GetItem(const nsString& aKey, LSValue& aValue) const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mClosed);
if (!mValues.Get(aKey, &aValue)) {
aValue.SetIsVoid(true);
}
}
void Datastore::GetKeys(nsTArray<nsString>& aKeys) const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mClosed);
for (auto item : mOrderedItems) {
aKeys.AppendElement(item.key());
}
}
void Datastore::SetItem(Database* aDatabase, const nsString& aKey,
const LSValue& aValue) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aDatabase);
MOZ_ASSERT(!mClosed);
MOZ_ASSERT(mInUpdateBatch);
LSValue oldValue;
GetItem(aKey, oldValue);
if (oldValue != aValue) {
bool isNewItem = oldValue.IsVoid();
NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem);
mValues.Put(aKey, aValue);
int64_t delta;
if (isNewItem) {
mWriteOptimizer.InsertItem(aKey, aValue);
int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
delta = sizeOfKey + static_cast<int64_t>(aValue.UTF16Length());
mUpdateBatchUsage += delta;
mSizeOfKeys += sizeOfKey;
mSizeOfItems += sizeOfKey + static_cast<int64_t>(aValue.Length());
} else {
mWriteOptimizer.UpdateItem(aKey, aValue);
delta = static_cast<int64_t>(aValue.UTF16Length()) -
static_cast<int64_t>(oldValue.UTF16Length());
mUpdateBatchUsage += delta;
mSizeOfItems += static_cast<int64_t>(aValue.Length()) -
static_cast<int64_t>(oldValue.Length());
}
if (IsPersistent()) {
mConnection->SetItem(aKey, aValue, delta, isNewItem);
}
}
}
void Datastore::RemoveItem(Database* aDatabase, const nsString& aKey) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aDatabase);
MOZ_ASSERT(!mClosed);
MOZ_ASSERT(mInUpdateBatch);
LSValue oldValue;
GetItem(aKey, oldValue);
if (!oldValue.IsVoid()) {
NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
mValues.Remove(aKey);
mWriteOptimizer.DeleteItem(aKey);
int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
int64_t delta = -sizeOfKey - static_cast<int64_t>(oldValue.UTF16Length());
mUpdateBatchUsage += delta;
mSizeOfKeys -= sizeOfKey;
mSizeOfItems -= sizeOfKey + static_cast<int64_t>(oldValue.Length());
if (IsPersistent()) {
mConnection->RemoveItem(aKey, delta);
}
}
}
void Datastore::Clear(Database* aDatabase) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mClosed);
if (mValues.Count()) {
int64_t delta = 0;
for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
const nsAString& key = iter.Key();
const LSValue& value = iter.Data();
delta += -static_cast<int64_t>(key.Length()) -
static_cast<int64_t>(value.UTF16Length());
NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
}
mValues.Clear();
if (mInUpdateBatch) {
mWriteOptimizer.Truncate();
mUpdateBatchUsage += delta;
} else {
mOrderedItems.Clear();
DebugOnly<bool> ok = UpdateUsage(delta);
MOZ_ASSERT(ok);
}
mSizeOfKeys = 0;
mSizeOfItems = 0;
if (IsPersistent()) {
mConnection->Clear(delta);
}
}
}
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();
}
mInUpdateBatch = true;
}
int64_t Datastore::EndUpdateBatch(int64_t aSnapshotPeakUsage) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mClosed);
MOZ_ASSERT(mInUpdateBatch);
mWriteOptimizer.ApplyAndReset(mOrderedItems);
MOZ_ASSERT(!mWriteOptimizer.HasWrites());
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<bool> ok = UpdateUsage(delta);
MOZ_ASSERT(ok);
}
}
}
int64_t result = mUpdateBatchUsage;
mUpdateBatchUsage = -1;
if (IsPersistent()) {
mConnection->EndUpdateBatch();
}
mInUpdateBatch = false;
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::HasOtherProcessObservers(Database* aDatabase) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aDatabase);
if (!gObservers) {
return false;
}
nsTArray<Observer*>* array;
if (!gObservers->Get(mOrigin, &array)) {
return false;
}
MOZ_ASSERT(array);
PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
for (Observer* observer : *array) {
if (observer->Manager() != databaseBackgroundActor) {
return true;
}
}
return false;
}
void Datastore::NotifyOtherProcessObservers(Database* aDatabase,
const nsString& aDocumentURI,
const nsString& aKey,
const LSValue& aOldValue,
const LSValue& aNewValue) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aDatabase);
if (!gObservers) {
return;
}
nsTArray<Observer*>* 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);
}
}
}
void Datastore::NoteChangedObserverArray(
const nsTArray<Observer*>& aObservers) {
AssertIsOnBackgroundThread();
for (auto iter = mActiveDatabases.ConstIter(); !iter.Done(); iter.Next()) {
Database* database = iter.Get()->GetKey();
Snapshot* snapshot = database->GetSnapshot();
MOZ_ASSERT(snapshot);
if (snapshot->IsDirty()) {
continue;
}
bool hasOtherProcessObservers = false;
PBackgroundParent* databaseBackgroundActor = database->Manager();
for (Observer* observer : aObservers) {
if (observer->Manager() != databaseBackgroundActor) {
hasOtherProcessObservers = true;
break;
}
}
if (snapshot->HasOtherProcessObservers() != hasOtherProcessObservers) {
snapshot->MarkDirty();
}
}
}
void Datastore::Stringify(nsACString& aResult) const {
AssertIsOnBackgroundThread();
aResult.AppendLiteral("DirectoryLock:");
aResult.AppendInt(!!mDirectoryLock);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("Connection:");
aResult.AppendInt(!!mConnection);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("QuotaObject:");
aResult.AppendInt(!!mQuotaObject);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("PrepareDatastoreOps:");
aResult.AppendInt(mPrepareDatastoreOps.Count());
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("PreparedDatastores:");
aResult.AppendInt(mPreparedDatastores.Count());
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("Databases:");
aResult.AppendInt(mDatabases.Count());
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("ActiveDatabases:");
aResult.AppendInt(mActiveDatabases.Count());
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("Origin:");
aResult.Append(AnonymizedOriginString(mOrigin));
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("PrivateBrowsingId:");
aResult.AppendInt(mPrivateBrowsingId);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("Closed:");
aResult.AppendInt(mClosed);
}
bool Datastore::UpdateUsage(int64_t aDelta) {
AssertIsOnBackgroundThread();
// Check internal LocalStorage origin limit.
int64_t newUsage = mUsage + aDelta;
MOZ_ASSERT(newUsage >= 0);
if (newUsage > StaticPrefs::dom_storage_default_quota() * 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;
bool databaseWasNotAvailable;
bool hasCreatedDatabase;
mConnection->GetFinishInfo(databaseWasNotAvailable, hasCreatedDatabase);
if (databaseWasNotAvailable && !hasCreatedDatabase) {
MOZ_ASSERT(mUsage == 0);
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
quotaManager->ResetUsageForClient(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin,
mozilla::dom::quota::Client::LS);
}
mConnection = nullptr;
// Now it's safe to release the directory lock and unregister itself from
// the hashtable.
mDirectoryLock = 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 LSValue& aOldValue, bool aAffectsOrder) {
AssertIsOnBackgroundThread();
for (auto iter = mDatabases.ConstIter(); !iter.Done(); iter.Next()) {
Database* database = iter.Get()->GetKey();
MOZ_ASSERT(database);
if (database == aDatabase) {
continue;
}
Snapshot* snapshot = database->GetSnapshot();
if (snapshot) {
snapshot->SaveItem(aKey, aOldValue, aAffectsOrder);
}
}
}
/*******************************************************************************
* PreparedDatastore
******************************************************************************/
void PreparedDatastore::Destroy() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(gPreparedDatastores);
MOZ_ASSERT(gPreparedDatastores->Get(mDatastoreId));
nsAutoPtr<PreparedDatastore> preparedDatastore;
gPreparedDatastores->Remove(mDatastoreId, &preparedDatastore);
MOZ_ASSERT(preparedDatastore);
}
// static
void PreparedDatastore::TimerCallback(nsITimer* aTimer, void* aClosure) {
AssertIsOnBackgroundThread();
auto* self = static_cast<PreparedDatastore*>(aClosure);
MOZ_ASSERT(self);
self->Destroy();
}
/*******************************************************************************
* Database
******************************************************************************/
Database::Database(const PrincipalInfo& aPrincipalInfo,
const Maybe<ContentParentId>& 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);
return;
}
if (NS_WARN_IF(!SendRequestAllowToClose()) && !mSnapshot) {
// This is not necessary, because there should be a runnable scheduled that
// will call ActorDestroy which calls AllowToClose. However we can speedup
// the shutdown a bit if we do it here directly, but only if there's no
// registered snapshot.
AllowToClose();
}
}
void Database::ForceKill() {
AssertIsOnBackgroundThread();
if (mActorDestroyed) {
MOZ_ASSERT(mAllowedToClose);
return;
}
Unused << PBackgroundLSDatabaseParent::Send__delete__(this);
}
void Database::Stringify(nsACString& aResult) const {
AssertIsOnBackgroundThread();
aResult.AppendLiteral("SnapshotRegistered:");
aResult.AppendInt(!!mSnapshot);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("OtherProcessActor:");
aResult.AppendInt(BackgroundParent::IsOtherProcessActor(Manager()));
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("Origin:");
aResult.Append(AnonymizedOriginString(mOrigin));
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("PrivateBrowsingId:");
aResult.AppendInt(mPrivateBrowsingId);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("AllowedToClose:");
aResult.AppendInt(mAllowedToClose);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("ActorDestroyed:");
aResult.AppendInt(mActorDestroyed);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("RequestedAllowToClose:");
aResult.AppendInt(mRequestedAllowToClose);
}
void Database::AllowToClose() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mAllowedToClose);
MOZ_ASSERT(mDatastore);
MOZ_ASSERT(!mSnapshot);
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 nsString& aKey,
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> snapshot = new Snapshot(this, aDocumentURI);
// Transfer ownership to IPDL.
return snapshot.forget().take();
}
mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor(
PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI,
const nsString& aKey, 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<Snapshot*>(aActor);
bool addKeyToUnknownItems;
nsTHashtable<nsStringHashKey> loadedItems;
nsTArray<LSItemInfo> itemInfos;
uint32_t nextLoadIndex;
LSSnapshot::LoadState loadState;
mDatastore->GetSnapshotLoadInfo(aKey, addKeyToUnknownItems, loadedItems,
itemInfos, nextLoadIndex, loadState);
nsTHashtable<nsStringHashKey> unknownItems;
if (addKeyToUnknownItems) {
unknownItems.PutEntry(aKey);
}
uint32_t totalLength = mDatastore->GetLength();
int64_t initialUsage = mDatastore->GetUsage();
int64_t peakUsage = initialUsage;
if (aIncreasePeakUsage) {
int64_t size = mDatastore->RequestUpdateUsage(aRequestedSize, aMinSize);
peakUsage += size;
}
bool hasOtherProcessObservers = mDatastore->HasOtherProcessObservers(this);
snapshot->Init(loadedItems, unknownItems, nextLoadIndex, totalLength,
initialUsage, peakUsage, loadState, hasOtherProcessObservers);
RegisterSnapshot(snapshot);
aInitInfo->addKeyToUnknownItems() = addKeyToUnknownItems;
aInitInfo->itemInfos() = std::move(itemInfos);
aInitInfo->totalLength() = totalLength;
aInitInfo->initialUsage() = initialUsage;
aInitInfo->peakUsage() = peakUsage;
aInitInfo->loadState() = loadState;
aInitInfo->hasOtherProcessObservers() = hasOtherProcessObservers;
return IPC_OK();
}
bool Database::DeallocPBackgroundLSSnapshotParent(
PBackgroundLSSnapshotParent* aActor) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
// Transfer ownership back from IPDL.
RefPtr<Snapshot> actor = dont_AddRef(static_cast<Snapshot*>(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 LSValue& aOldValue,
bool aAffectsOrder) {
AssertIsOnBackgroundThread();
MarkDirty();
if (mLoadedAllItems) {
return;
}
if (!mLoadedItems.GetEntry(aKey) && !mUnknownItems.GetEntry(aKey)) {
LSValue oldValue(aOldValue);
mValues.LookupForAdd(aKey).OrInsert([oldValue]() { return oldValue; });
}
if (aAffectsOrder && !mSavedKeys) {
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<LSWriteInfo>&& 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);
}
if (NS_WARN_IF(mHasOtherProcessObservers)) {
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, info.key(), info.value());
break;
}
case LSWriteInfo::TLSRemoveItemInfo: {
const LSRemoveItemInfo& info = writeInfo.get_LSRemoveItemInfo();
mDatastore->RemoveItem(mDatabase, info.key());
break;
}
case LSWriteInfo::TLSClearInfo: {
mDatastore->Clear(mDatabase);
break;
}
default:
MOZ_CRASH("Should never get here!");
}
}
mUsage = mDatastore->EndUpdateBatch(-1);
return IPC_OK();
}
mozilla::ipc::IPCResult Snapshot::RecvCheckpointAndNotify(
nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mUsage >= 0);
MOZ_ASSERT(mPeakUsage >= mUsage);
if (NS_WARN_IF(aWriteAndNotifyInfos.IsEmpty())) {
ASSERT_UNLESS_FUZZING();
return IPC_FAIL_NO_REASON(this);
}
if (NS_WARN_IF(!mHasOtherProcessObservers)) {
ASSERT_UNLESS_FUZZING();
return IPC_FAIL_NO_REASON(this);
}
mDatastore->BeginUpdateBatch(mUsage);
for (uint32_t index = 0; index < aWriteAndNotifyInfos.Length(); index++) {
const LSWriteAndNotifyInfo& writeAndNotifyInfo =
aWriteAndNotifyInfos[index];
switch (writeAndNotifyInfo.type()) {
case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
const LSSetItemAndNotifyInfo& info =
writeAndNotifyInfo.get_LSSetItemAndNotifyInfo();
mDatastore->SetItem(mDatabase, info.key(), info.value());
mDatastore->NotifyOtherProcessObservers(
mDatabase, mDocumentURI, info.key(), info.oldValue(), info.value());
break;
}
case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
const LSRemoveItemAndNotifyInfo& info =
writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo();
mDatastore->RemoveItem(mDatabase, info.key());
mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
info.key(), info.oldValue(),
VoidLSValue());
break;
}
case LSWriteAndNotifyInfo::TLSClearInfo: {
mDatastore->Clear(mDatabase);
mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
VoidString(), VoidLSValue(),
VoidLSValue());
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::RecvLoadValueAndMoreItems(
const nsString& aKey, LSValue* aValue, nsTArray<LSItemInfo>* aItemInfos) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aValue);
MOZ_ASSERT(aItemInfos);
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);
// mLoadedItems.Count()==mTotalLength is checked below.
}
// Load some more key/value pairs (as many as the snapshot gradual prefill
// byte budget allows).
if (gSnapshotGradualPrefill > 0) {
const nsTArray<LSItemInfo>& orderedItems = mDatastore->GetOrderedItems();
uint32_t length;
if (mSavedKeys) {
length = mKeys.Length();
} else {
length = orderedItems.Length();
}
int64_t size = 0;
while (mNextLoadIndex < length) {
// If the datastore's ordering has changed, mSavedKeys will be true and
// mKeys contains an ordered list of the keys. Otherwise we can use the
// datastore's key ordering which is still the same as when the snapshot
// was created.
nsString key;
if (mSavedKeys) {
key = mKeys[mNextLoadIndex];
} else {
key = orderedItems[mNextLoadIndex].key();
}
// Normally we would do this:
// if (!mLoadedItems.GetEntry(key)) {
// ...
// mLoadedItems.PutEntry(key);
// }
// but that requires two hash lookups. We can reduce that to just one
// hash lookup if we always call PutEntry and check the number of entries
// before and after the put (which is very cheap). However, if we reach
// the prefill limit, we need to call RemoveEntry, but that is also cheap
// because we pass the entry (not the key).
uint32_t countBeforePut = mLoadedItems.Count();
auto loadedItemEntry = mLoadedItems.PutEntry(key);
if (countBeforePut != mLoadedItems.Count()) {
// Check mValues first since that contains values as they existed when
// our snapshot was created, but have since been changed/removed in the
// datastore. If it's not there, then the datastore has the
// still-current value. However, if the datastore's key ordering has
// changed, we need to do a hash lookup rather than being able to do an
// optimized direct access to the index.
LSValue value;
auto valueEntry = mValues.Lookup(key);
if (valueEntry) {
value = valueEntry.Data();
} else if (mSavedKeys) {
mDatastore->GetItem(nsString(key), value);
} else {
value = orderedItems[mNextLoadIndex].value();
}
// All not loaded keys must have a value.
MOZ_ASSERT(!value.IsVoid());
size += static_cast<int64_t>(key.Length()) +
static_cast<int64_t>(value.Length());
if (size > gSnapshotGradualPrefill) {
mLoadedItems.RemoveEntry(loadedItemEntry);
// mNextLoadIndex is not incremented, so we will resume at the same
// position next time.
break;
}
if (valueEntry) {
valueEntry.Remove();
}
LSItemInfo* itemInfo = aItemInfos->AppendElement();
itemInfo->key() = key;
itemInfo->value() = value;
}
mNextLoadIndex++;
}
}
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<nsString>* 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 LSValue& aOldValue,
const LSValue& 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<Observer*>* array;
gObservers->Get(mOrigin, &array);
MOZ_ASSERT(array);
array->RemoveElement(this);
if (RefPtr<Datastore> datastore = GetDatastore(mOrigin)) {
datastore->NoteChangedObserverArray(*array);
}
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,
const LSRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId)
: mMainEventTarget(aMainEventTarget),
mParams(aParams),
mContentParentId(aContentParentId),
mState(State::Initial),
mWaitingForFinish(false) {}
LSRequestBase::~LSRequestBase() {
MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
mState == State::Initial || mState == State::Completed);
}
void LSRequestBase::Dispatch() {
AssertIsOnOwningThread();
mState = State::StartingRequest;
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
}
void LSRequestBase::StringifyState(nsACString& aResult) const {
AssertIsOnOwningThread();
switch (mState) {
case State::Initial:
aResult.AppendLiteral("Initial");
return;
case State::StartingRequest:
aResult.AppendLiteral("StartingRequest");
return;
case State::Nesting:
aResult.AppendLiteral("Nesting");
return;
case State::SendingReadyMessage:
aResult.AppendLiteral("SendingReadyMessage");
return;
case State::WaitingForFinish:
aResult.AppendLiteral("WaitingForFinish");
return;
case State::SendingResults:
aResult.AppendLiteral("SendingResults");
return;
case State::Completed:
aResult.AppendLiteral("Completed");
return;
default:
MOZ_CRASH("Bad state!");
}
}
void LSRequestBase::Stringify(nsACString& aResult) const {
AssertIsOnOwningThread();
aResult.AppendLiteral("State:");
StringifyState(aResult);
}
void LSRequestBase::Log() {
AssertIsOnOwningThread();
if (!LS_LOG_TEST()) {
return;
}
LS_LOG(("LSRequestBase [%p]", this));
nsCString state;
StringifyState(state);
LS_LOG((" mState: %s", state.get()));
}
nsresult LSRequestBase::NestedRun() { return NS_OK; }
bool LSRequestBase::VerifyRequestParams() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mParams.type() != LSRequestParams::T__None);
switch (mParams.type()) {
case LSRequestParams::TLSRequestPreloadDatastoreParams: {
const LSRequestCommonParams& params =
mParams.get_LSRequestPreloadDatastoreParams().commonParams();
if (NS_WARN_IF(!VerifyPrincipalInfo(params.principalInfo(),
params.storagePrincipalInfo()))) {
return false;
}
if (NS_WARN_IF(
!VerifyOriginKey(params.originKey(), params.principalInfo()))) {
return false;
}
break;
}
case LSRequestParams::TLSRequestPrepareDatastoreParams: {
const LSRequestPrepareDatastoreParams& params =
mParams.get_LSRequestPrepareDatastoreParams();
const LSRequestCommonParams& commonParams = params.commonParams();
if (NS_WARN_IF(
!VerifyPrincipalInfo(commonParams.principalInfo(),
commonParams.storagePrincipalInfo()))) {
return false;
}
if (NS_WARN_IF(!VerifyClientId(mContentParentId,
commonParams.principalInfo(),
params.clientId()))) {
return false;
}
if (NS_WARN_IF(!VerifyOriginKey(commonParams.originKey(),
commonParams.principalInfo()))) {
return false;
}
break;
}
case LSRequestParams::TLSRequestPrepareObserverParams: {
const LSRequestPrepareObserverParams& params =
mParams.get_LSRequestPrepareObserverParams();
if (NS_WARN_IF(!VerifyPrincipalInfo(params.principalInfo(),
params.storagePrincipalInfo()))) {
return false;
}
if (NS_WARN_IF(!VerifyClientId(mContentParentId, params.principalInfo(),
params.clientId()))) {
return false;
}
break;
}
default:
MOZ_CRASH("Should never get here!");
}
return true;
}
nsresult LSRequestBase::StartRequest() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::StartingRequest);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
!MayProceed()) {
return NS_ERROR_FAILURE;
}
#ifdef DEBUG
// Always verify parameters in DEBUG builds!
bool trustParams = false;
#else
bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
#endif
if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) {
return NS_ERROR_FAILURE;
}
nsresult rv = Start();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void LSRequestBase::SendReadyMessage() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::SendingReadyMessage);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
!MayProceed()) {
MaybeSetFailureCode(NS_ERROR_FAILURE);
}
nsresult rv = SendReadyMessageInternal();
if (NS_WARN_IF(NS_FAILED(rv))) {
MaybeSetFailureCode(rv);
FinishInternal();
}
}
nsresult LSRequestBase::SendReadyMessageInternal() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::SendingReadyMessage);
if (!MayProceed()) {
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(!SendReady())) {
return NS_ERROR_FAILURE;
}
mState = State::WaitingForFinish;
mWaitingForFinish = true;
return NS_OK;
}
void LSRequestBase::Finish() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::WaitingForFinish);
mWaitingForFinish = false;
FinishInternal();
}
void LSRequestBase::FinishInternal() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::SendingReadyMessage ||
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<LSRequestBase> kungFuDeathGrip = this;
MOZ_ALWAYS_SUCCEEDS(this->Run());
}
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);
MOZ_ASSERT(response.type() != LSRequestResponse::T__None);
if (response.type() == LSRequestResponse::Tnsresult) {
MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
SetFailureCode(response.get_nsresult());
}
} else {
response = ResultCode();
}
Unused << PBackgroundLSRequestParent::Send__delete__(this, response);
}
Cleanup();
mState = State::Completed;
}
NS_IMETHODIMP
LSRequestBase::Run() {
nsresult rv;
switch (mState) {
case State::StartingRequest:
rv = StartRequest();
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();
// Assume ActorDestroy can happen at any time, so we can't probe the current
// state since mState can be modified on any thread (only one thread at a time
// based on the state machine). However we can use mWaitingForFinish which is
// only touched on the owning thread. If mWaitingForFinisg is true, we can
// also modify mState since we are guaranteed that there are no pending
// runnables which would probe mState to decide what code needs to run (there
// shouldn't be any running runnables on other threads either).
if (mWaitingForFinish) {
Finish();
}
// We don't have to handle the case when mWaitingForFinish is not true since
// it means that either nothing has been initialized yet, so nothing to
// cleanup or there are pending runnables that will detect that the actor has
// been destroyed and cleanup accordingly.
}
mozilla::ipc::IPCResult LSRequestBase::RecvCancel() {
AssertIsOnOwningThread();
Log();
const char* crashOnCancel = PR_GetEnv("LSNG_CRASH_ON_CANCEL");
if (crashOnCancel) {
MOZ_CRASH("LSNG: Crash on cancel.");
}
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();
Finish();
return IPC_OK();
}
/*******************************************************************************
* PrepareDatastoreOp
******************************************************************************/
PrepareDatastoreOp::PrepareDatastoreOp(
nsIEventTarget* aMainEventTarget, const LSRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId)
: LSRequestBase(aMainEventTarget, aParams, aContentParentId),
mMainEventTarget(aMainEventTarget),
mLoadDataOp(nullptr),
mPrivateBrowsingId(0),
mUsage(0),
mSizeOfKeys(0),
mSizeOfItems(0),
mDatastoreId(0),
mNestedState(NestedState::BeforeNesting),
mForPreload(aParams.type() ==
LSRequestParams::TLSRequestPreloadDatastoreParams),
mDatabaseNotAvailable(false),
mRequestedDirectoryLock(false),
mInvalidated(false)
#ifdef DEBUG
,
mDEBUGUsage(0)
#endif
{
MOZ_ASSERT(
aParams.type() == LSRequestParams::TLSRequestPreloadDatastoreParams ||
aParams.type() == LSRequestParams::TLSRequestPrepareDatastoreParams);
}
PrepareDatastoreOp::~PrepareDatastoreOp() {
MOZ_ASSERT(!mDirectoryLock);
MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
mState == State::Initial || mState == State::Completed);
MOZ_ASSERT(!mLoadDataOp);
}
void PrepareDatastoreOp::StringifyNestedState(nsACString& aResult) const {
AssertIsOnOwningThread();
switch (mNestedState) {
case NestedState::BeforeNesting:
aResult.AppendLiteral("BeforeNesting");
return;
case NestedState::CheckExistingOperations:
aResult.AppendLiteral("CheckExistingOperations");
return;
case NestedState::CheckClosingDatastore:
aResult.AppendLiteral("CheckClosingDatastore");
return;
case NestedState::PreparationPending:
aResult.AppendLiteral("PreparationPending");
return;
case NestedState::QuotaManagerPending:
aResult.AppendLiteral("QuotaManagerPending");
return;
case NestedState::DirectoryOpenPending:
aResult.AppendLiteral("DirectoryOpenPending");
return;
case NestedState::DatabaseWorkOpen:
aResult.AppendLiteral("DatabaseWorkOpen");
return;
case NestedState::BeginLoadData:
aResult.AppendLiteral("BeginLoadData");
return;
case NestedState::DatabaseWorkLoadData:
aResult.AppendLiteral("DatabaseWorkLoadData");
return;
case NestedState::AfterNesting:
aResult.AppendLiteral("AfterNesting");
return;
default:
MOZ_CRASH("Bad state!");
}
}
void PrepareDatastoreOp::Stringify(nsACString& aResult) const {
AssertIsOnOwningThread();
LSRequestBase::Stringify(aResult);
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("Origin:");
aResult.Append(AnonymizedOriginString(mOrigin));
aResult.Append(kQuotaGenericDelimiter);
aResult.AppendLiteral("NestedState:");
StringifyNestedState(aResult);
}
void PrepareDatastoreOp::Log() {
AssertIsOnOwningThread();
LSRequestBase::Log();
if (!LS_LOG_TEST()) {
return;
}
nsCString nestedState;
StringifyNestedState(nestedState);
LS_LOG((" mNestedState: %s", nestedState.get()));
switch (mNestedState) {
case NestedState::CheckClosingDatastore: {
for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0;
index--) {
PrepareDatastoreOp* existingOp = (*gPrepareDatastoreOps)[index - 1];
if (existingOp->mDelayedOp == this) {
LS_LOG((" mDelayedBy: [%p]", existingOp));
existingOp->Log();
break;
}
}
break;
}
case NestedState::DirectoryOpenPending: {
MOZ_ASSERT(mPendingDirectoryLock);
LS_LOG((" mPendingDirectoryLock: [%p]", mPendingDirectoryLock.get()));
mPendingDirectoryLock->Log();
break;
}
default:;
}
}
nsresult PrepareDatastoreOp::Start() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::StartingRequest);
MOZ_ASSERT(mNestedState == NestedState::BeforeNesting);
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
MOZ_ASSERT(MayProceed());
const LSRequestCommonParams& commonParams =
mForPreload
? mParams.get_LSRequestPreloadDatastoreParams().commonParams()
: mParams.get_LSRequestPrepareDatastoreParams().commonParams();
const PrincipalInfo& storagePrincipalInfo =
commonParams.storagePrincipalInfo();
if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin);
} else {
MOZ_ASSERT(storagePrincipalInfo.type() ==
PrincipalInfo::TContentPrincipalInfo);
QuotaManager::GetInfoFromValidatedPrincipalInfo(
storagePrincipalInfo, &mSuffix, &mGroup, &mMainThreadOrigin);
}
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;
}
const LSRequestCommonParams& commonParams =
mForPreload
? mParams.get_LSRequestPreloadDatastoreParams().commonParams()
: mParams.get_LSRequestPrepareDatastoreParams().commonParams();
const PrincipalInfo& storagePrincipalInfo =
commonParams.storagePrincipalInfo();
nsCString originAttrSuffix;
uint32_t privateBrowsingId;
if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
privateBrowsingId = 0;
} else {
MOZ_ASSERT(storagePrincipalInfo.type() ==
PrincipalInfo::TContentPrincipalInfo);
const ContentPrincipalInfo& info =
storagePrincipalInfo.get_ContentPrincipalInfo();
const OriginAttributes& attrs = info.attrs();
attrs.CreateSuffix(originAttrSuffix);
privateBrowsingId = attrs.mPrivateBrowsingId;
}
mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(
originAttrSuffix, commonParams.originKey());
MOZ_ASSERT(mArchivedOriginScope);
// 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());
mPrivateBrowsingId = privateBrowsingId;
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> datastore;
if ((datastore = GetDatastore(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 ((mDatastore = GetDatastore(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;
RefPtr<DirectoryLock> pendingDirectoryLock =
QuotaManager::Get()->CreateDirectoryLock(PERSISTENCE_TYPE_DEFAULT, mGroup,
mOrigin,
mozilla::dom::quota::Client::LS,
/* aExclusive */ false, this);
MOZ_ASSERT(pendingDirectoryLock);
if (mNestedState == NestedState::DirectoryOpenPending) {
mPendingDirectoryLock = pendingDirectoryLock.forget();
}
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(mUsage == 0);
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);
// This must be called before EnsureTemporaryStorageIsInitialized.
nsresult rv = quotaManager->EnsureStorageIsInitialized();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// This ensures that usages for existings origin directories are cached in
// memory.
rv = quotaManager->EnsureTemporaryStorageIsInitialized();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
uint64_t usage;
bool hasUsage =
quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin,
mozilla::dom::quota::Client::LS, usage);
if (!gArchivedOrigins) {
rv = LoadArchivedOrigins();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(gArchivedOrigins);
}
bool hasDataForMigration = mArchivedOriginScope->HasMatches(gArchivedOrigins);
// If there's nothing to preload (except the case when we want to migrate data
// during preloading), then we can finish the operation without creating a
// datastore in GetResponse (GetResponse won't create a datastore if
// mDatatabaseNotAvailable and mForPreload are both true).
if (mForPreload && !hasUsage && !hasDataForMigration) {
return DatabaseNotAvailable();
}
// The origin directory doesn't need to be created when we don't have data for
// migration. It will be created on the connection thread in
// Connection::EnsureStorageConnection.
// However, origin quota must be initialized, GetQuotaObject in GetResponse
// would fail otherwise.
nsCOMPtr<nsIFile> directoryEntry;
if (hasDataForMigration) {
rv = quotaManager->EnsureStorageAndOriginIsInitialized(
PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin,
mozilla::dom::quota::Client::LS, getter_AddRefs(directoryEntry));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else {
rv = quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_DEFAULT, mOrigin,
getter_AddRefs(directoryEntry));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
quotaManager->EnsureQuotaForOrigin(PERSISTENCE_TYPE_DEFAULT, mGroup,
mOrigin);
}
rv = directoryEntry->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsString directoryPath;
rv = directoryEntry->GetPath(directoryPath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// The ls directory doesn't need to be created when we don't have data for
// migration. It will be created on the connection thread in
// Connection::EnsureStorageConnection.
rv = EnsureDirectoryEntry(directoryEntry,
/* aCreateIfNotExists */ hasDataForMigration,
/* aIsDirectory */ true);
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;
}
rv = directoryEntry->GetPath(mDatabaseFilePath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// The database doesn't need to be created when we don't have data for
// migration. It will be created on the connection thread in
// Connection::EnsureStorageConnection.
bool alreadyExisted;
rv = EnsureDirectoryEntry(directoryEntry,
/* aCreateIfNotExists */ hasDataForMigration,
/* aIsDirectory */ false, &alreadyExisted);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (alreadyExisted) {
// The database does exist.
MOZ_ASSERT(hasUsage);
mUsage = usage;
} else {
// The database doesn't exist.
MOZ_ASSERT(!hasUsage);
if (!hasDataForMigration) {
// The database doesn't exist and we don't have data for migration.
// Finish the operation, but create an empty datastore in GetResponse
// (GetResponse will create an empty datastore if mDatabaseNotAvailable
// is true and mForPreload is false).
return DatabaseNotAvailable();
}
}
// We initialized mDatabaseFilePath and mUsage, GetQuotaObject can be called
// from now on.
RefPtr<QuotaObject> quotaObject;
nsCOMPtr<nsIFile> usageFile;
rv = GetUsageFile(directoryPath, getter_AddRefs(usageFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIFile> usageJournalFile;
rv = GetUsageJournalFile(directoryPath, getter_AddRefs(usageJournalFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageConnection> 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) {
if (!quotaObject) {
quotaObject = GetQuotaObject();
if (!quotaObject) {
return NS_ERROR_FAILURE;
}
}
MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
mUsage = 0;
}
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;
}
if (!quotaObject) {
quotaObject = GetQuotaObject();
if (!quotaObject) {
return NS_ERROR_FAILURE;
}
}
if (!quotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) {
return NS_ERROR_FILE_NO_DEVICE_SPACE;
}
auto autoUpdateSize = MakeScopeExit([&quotaObject] {
MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
});
mozStorageTransaction transaction(
connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
nsCOMPtr<mozIStorageFunction> function = new CompressFunction();
rv =
connection->CreateFunction(NS_LITERAL_CSTRING("compress"), 1, function);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
function = new CompressibleFunction();
rv = connection->CreateFunction(NS_LITERAL_CSTRING("compressible"), 1,
function);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageStatement> stmt;
rv = connection->CreateStatement(
NS_LITERAL_CSTRING(
"INSERT INTO data (key, value, utf16Length, compressed) "
"SELECT key, compress(value), utf16Length(value), "
"compressible(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->RemoveFunction(NS_LITERAL_CSTRING("compress"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = connection->RemoveFunction(NS_LITERAL_CSTRING("compressible"));
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 = UpdateUsageFile(usageFile, usageJournalFile, newUsage);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = transaction.Commit();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
autoUpdateSize.release();
rv = usageJournalFile->Remove(false);
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;
}
nsCOMPtr<mozIStorageConnection> 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) {
if (aAlreadyExisted) {
*aAlreadyExisted = false;
}
return NS_OK;
}
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<mozIStorageStatement> 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<QuotaObject> 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> quotaObject = quotaManager->GetQuotaObject(
PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin,
mozilla::dom::quota::Client::LS, mDatabaseFilePath, mUsage);
if (!quotaObject) {
LS_WARNING("Failed to get quota object for group (%s) and origin (%s)!",
mGroup.get(), mOrigin.get());
}
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(
mSuffix, mGroup, mOrigin, std::move(mArchivedOriginScope),
/* aDatabaseWasNotAvailable */ false);
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> 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()));
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
MOZ_ASSERT(MayProceed());
// A datastore is not created when we are just trying to preload data and
// there's no database file.
if (mDatabaseNotAvailable && mForPreload) {
LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
aResponse = preloadDatastoreResponse;
return;
}
if (!mDatastore) {
MOZ_ASSERT(mUsage == mDEBUGUsage);
RefPtr<QuotaObject> quotaObject;
if (mPrivateBrowsingId == 0) {
if (!mConnection) {
// This can happen when there's no database file.
MOZ_ASSERT(mDatabaseNotAvailable);
// Even though there's no database file, we need to create a connection
// and pass it to datastore.
if (!gConnectionThread) {
gConnectionThread = new ConnectionThread();
}
mConnection = gConnectionThread->CreateConnection(
mSuffix, mGroup, mOrigin, std::move(mArchivedOriginScope),
/* aDatabaseWasNotAvailable */ true);
MOZ_ASSERT(mConnection);
}
quotaObject = GetQuotaObject();
if (!quotaObject) {
aResponse = NS_ERROR_FAILURE;
return;
}
}
mDatastore = new Datastore(mGroup, 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);
}
mDatastoreId = ++gLastDatastoreId;
nsAutoPtr<PreparedDatastore> preparedDatastore(
new PreparedDatastore(mDatastore, mContentParentId, mOrigin, mDatastoreId,
/* aForPreload */ mForPreload));
if (!gPreparedDatastores) {
gPreparedDatastores = new PreparedDatastoreHashtable();
}
gPreparedDatastores->Put(mDatastoreId, preparedDatastore);
if (mInvalidated) {
preparedDatastore->Invalidate();
}
preparedDatastore.forget();
if (mForPreload) {
LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
aResponse = preloadDatastoreResponse;
} else {
LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
prepareDatastoreResponse.datastoreId() = mDatastoreId;
aResponse = prepareDatastoreResponse;
}
}
void PrepareDatastoreOp::Cleanup() {
AssertIsOnOwningThread();
if (mDatastore) {
MOZ_ASSERT(!mDirectoryLock);
MOZ_ASSERT(!mConnection);
if (NS_FAILED(ResultCode()) && mDatastoreId > 0) {
// Just in case we failed to send datastoreId to the child, we need to
// destroy prepared datastore, otherwise it won't be destroyed until the
// timer fires (after 20 seconds).
MOZ_ASSERT(gPreparedDatastores);
MOZ_ASSERT(gPreparedDatastores->Get(mDatastoreId));
nsAutoPtr<PreparedDatastore> preparedDatastore;
gPreparedDatastores->Remove(mDatastoreId, &preparedDatastore);
MOZ_ASSERT(preparedDatastore);
}
// 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<nsIRunnable> 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);
mPendingDirectoryLock = nullptr;
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);
mPendingDirectoryLock = nullptr;
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, utf16Length, compressed "
"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;
}
LSValue value;
rv = value.InitFromStatement(stmt, 1);
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.UTF16Length();
#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();
}
NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressFunction, mozIStorageFunction)
NS_IMETHODIMP
PrepareDatastoreOp::CompressFunction::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
AssertIsOnIOThread();
MOZ_ASSERT(aFunctionArguments);
MOZ_ASSERT(aResult);
#ifdef DEBUG
{
uint32_t argCount;
MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount));
MOZ_ASSERT(argCount == 1);
int32_t type;
MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type));
MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
}
#endif
nsCString value;
nsresult rv = aFunctionArguments->GetUTF8String(0, value);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString compressed;
if (NS_WARN_IF(!SnappyCompress(value, compressed))) {
return NS_ERROR_FAILURE;
}
if (!compressed.IsVoid()) {
value = compressed;
}
nsCOMPtr<nsIVariant> result = new storage::UTF8TextVariant(value);
result.forget(aResult);
return NS_OK;
}
NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressibleFunction, mozIStorageFunction)
NS_IMETHODIMP
PrepareDatastoreOp::CompressibleFunction::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
AssertIsOnIOThread();
MOZ_ASSERT(aFunctionArguments);
MOZ_ASSERT(aResult);
#ifdef DEBUG
{
uint32_t argCount;
MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount));
MOZ_ASSERT(argCount == 1);
int32_t type;
MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type));
MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
}
#endif
nsCString value;
nsresult rv = aFunctionArguments->GetUTF8String(0, value);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString compressed;
if (NS_WARN_IF(!SnappyCompress(value, compressed))) {
return NS_ERROR_FAILURE;
}
bool compressible = !compressed.IsVoid();
nsCOMPtr<nsIVariant> result = new storage::IntegerVariant(compressible);
result.forget(aResult);
return NS_OK;
}
/*******************************************************************************
* PrepareObserverOp
******************************************************************************/
PrepareObserverOp::PrepareObserverOp(
nsIEventTarget* aMainEventTarget, const LSRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId)
: LSRequestBase(aMainEventTarget, aParams, aContentParentId) {
MOZ_ASSERT(aParams.type() ==
LSRequestParams::TLSRequestPrepareObserverParams);
}
nsresult PrepareObserverOp::Start() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::StartingRequest);
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
MOZ_ASSERT(MayProceed());
const LSRequestPrepareObserverParams params =
mParams.get_LSRequestPrepareObserverParams();
const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
QuotaManager::GetInfoForChrome(nullptr, nullptr, &mOrigin);
} else {
MOZ_ASSERT(storagePrincipalInfo.type() ==
PrincipalInfo::TContentPrincipalInfo);
QuotaManager::GetInfoFromValidatedPrincipalInfo(storagePrincipalInfo,
nullptr, nullptr, &mOrigin);
}
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()));
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
MOZ_ASSERT(MayProceed());
uint64_t observerId = ++gLastObserverId;
RefPtr<Observer> observer = new Observer(mOrigin);
if (!gPreparedObsevers) {
gPreparedObsevers = new PreparedObserverHashtable();
}
gPreparedObsevers->Put(observerId, observer);
LSRequestPrepareObserverResponse prepareObserverResponse;
prepareObserverResponse.observerId() = observerId;
aResponse = prepareObserverResponse;
}
/*******************************************************************************
+ * LSSimpleRequestBase
+
******************************************************************************/
LSSimpleRequestBase::LSSimpleRequestBase(
const LSSimpleRequestParams& aParams,
const Maybe<ContentParentId>& aContentParentId)
: mParams(aParams),
mContentParentId(aContentParentId),
mState(State::Initial) {}
LSSimpleRequestBase::~LSSimpleRequestBase() {
MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
mState == State::Initial || mState == State::Completed);
}
void LSSimpleRequestBase::Dispatch() {
AssertIsOnOwningThread();
mState = State::StartingRequest;
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
}
bool LSSimpleRequestBase::VerifyRequestParams() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mParams.type() != LSSimpleRequestParams::T__None);
switch (mParams.type()) {
case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
const LSSimpleRequestPreloadedParams& params =
mParams.get_LSSimpleRequestPreloadedParams();
if (NS_WARN_IF(!VerifyPrincipalInfo(params.principalInfo(),
params.storagePrincipalInfo()))) {
return false;
}
break;
}
default:
MOZ_CRASH("Should never get here!");
}
return true;
}
nsresult LSSimpleRequestBase::StartRequest() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::StartingRequest);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
!MayProceed()) {
return NS_ERROR_FAILURE;
}
#ifdef DEBUG
// Always verify parameters in DEBUG builds!
bool trustParams = false;
#else
bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
#endif
if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) {
return NS_ERROR_FAILURE;
}
nsresult rv = Start();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
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::StartingRequest:
rv = StartRequest();
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,
const Maybe<ContentParentId>& aContentParentId)
: LSSimpleRequestBase(aParams, aContentParentId) {
MOZ_ASSERT(aParams.type() ==
LSSimpleRequestParams::TLSSimpleRequestPreloadedParams);
}
nsresult PreloadedOp::Start() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::StartingRequest);
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
MOZ_ASSERT(MayProceed());
const LSSimpleRequestPreloadedParams& params =
mParams.get_LSSimpleRequestPreloadedParams();
const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
QuotaManager::GetInfoForChrome(nullptr, nullptr, &mOrigin);
} else {
MOZ_ASSERT(storagePrincipalInfo.type() ==
PrincipalInfo::TContentPrincipalInfo);
QuotaManager::GetInfoFromValidatedPrincipalInfo(storagePrincipalInfo,
nullptr, nullptr, &mOrigin);
}
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()));
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
MOZ_ASSERT(MayProceed());
bool preloaded;
RefPtr<Datastore> datastore;
if ((datastore = GetDatastore(mOrigin)) && !datastore->IsClosed()) {
preloaded = true;
} else {
preloaded = false;
}
LSSimpleRequestPreloadedResponse preloadedResponse;
preloadedResponse.preloaded() = preloaded;
aResponse = preloadedResponse;
}
/*******************************************************************************
* ArchivedOriginScope
******************************************************************************/
// static
ArchivedOriginScope* ArchivedOriginScope::CreateFromOrigin(
const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey) {
return new ArchivedOriginScope(
std::move(Origin(aOriginAttrSuffix, aOriginKey)));
}
// static
ArchivedOriginScope* ArchivedOriginScope::CreateFromPrefix(
const nsACString& aOriginKey) {
return new ArchivedOriginScope(std::move(Prefix(aOriginKey)));
}
// 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 operator()(const Origin& aOrigin) {
*mBindingClause = NS_LITERAL_CSTRING(
" WHERE originKey = :originKey "
"AND originAttributes = :originAttributes");
}
void operator()(const Prefix& aPrefix) {
*mBindingClause = NS_LITERAL_CSTRING(" WHERE originKey = :originKey");
}
void operator()(const Pattern& aPattern) {
*mBindingClause = NS_LITERAL_CSTRING(
" WHERE originAttributes MATCH :originAttributesPattern");
}
void operator()(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 operator()(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 operator()(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 operator()(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 operator()(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 operator()(const Origin& aOrigin) {
nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(),
aOrigin.OriginNoSuffix());
ArchivedOriginInfo* archivedOriginInfo;
return mHashtable->Get(hashKey, &archivedOriginInfo);
}
bool operator()(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 operator()(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 operator()(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 operator()(const Origin& aOrigin) {
nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(),
aOrigin.OriginNoSuffix());
mHashtable->Remove(hashKey);
}
void operator()(const Prefix& aPrefix) {
for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) {
ArchivedOriginInfo* archivedOriginInfo = iter.Data();
if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) {
iter.Remove();
}
}
}
void operator()(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 operator()(const Null& aNull) { mHashtable->Clear(); }
};
mData.match(Matcher(aHashtable));
}
/*******************************************************************************
* 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, bool aForGetUsage) {
AssertIsOnIOThread();
MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
nsCOMPtr<nsIFile> directory;
nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin,
getter_AddRefs(directory));
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, 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(kQuotaExternalError, LS_Append);
return rv;
}
#ifdef DEBUG
bool exists;
rv = directory->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, 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(kQuotaExternalError, LS_GetPath);
return rv;
}
nsCOMPtr<nsIFile> usageFile;
rv = GetUsageFile(directoryPath, getter_AddRefs(usageFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, 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(kQuotaExternalError, LS_IsDirectory);
return rv;
}
if (NS_WARN_IF(isDirectory)) {
REPORT_TELEMETRY_INIT_ERR(kQuotaInternalError, LS_UnexpectedDir);
return NS_ERROR_FAILURE;
}
usageFileExists = true;
} else {
usageFileExists = false;
}
nsCOMPtr<nsIFile> usageJournalFile;
rv = GetUsageJournalFile(directoryPath, getter_AddRefs(usageJournalFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, 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(kQuotaExternalError, LS_IsDirectory2);
return rv;
}
if (NS_WARN_IF(isDirectory)) {
REPORT_TELEMETRY_INIT_ERR(kQuotaInternalError, LS_UnexpectedDir2);
return NS_ERROR_FAILURE;
}
if (usageFileExists) {
rv = usageFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_Remove);
return rv;
}
usageFileExists = false;
}
rv = usageJournalFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_Remove2);
return rv;
}
}
nsCOMPtr<nsIFile> file;
rv = directory->Clone(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_Clone);
return rv;
}
rv = file->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, 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(kQuotaExternalError, LS_IsDirectory3);
return rv;
}
if (NS_WARN_IF(isDirectory)) {
REPORT_TELEMETRY_INIT_ERR(kQuotaInternalError, LS_UnexpectedDir3);
return NS_ERROR_FAILURE;
}
int64_t usage;
rv = LoadUsageFile(usageFile, &usage);
if (NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<mozIStorageConnection> connection;
bool dummy;
rv = CreateStorageConnection(file, usageFile, aOrigin,
getter_AddRefs(connection), &dummy);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_CreateConnection);
return rv;
}
rv = GetUsage(connection, /* aArchivedOriginScope */ nullptr, &usage);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_GetUsage);
return rv;
}
rv = UpdateUsageFile(usageFile, usageJournalFile, usage);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_UpdateUsageFile);
return rv;
}
rv = usageJournalFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_Remove3);
return rv;
}
}
MOZ_ASSERT(usage >= 0);
aUsageInfo->AppendToDatabaseUsage(Some(uint64_t(usage)));
} else if (usageFileExists) {
rv = usageFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_Remove4);
return rv;
}
}
// Report unknown files in debug builds, but don't fail, just warn.
#ifdef DEBUG
nsCOMPtr<nsIDirectoryEnumerator> directoryEntries;
rv = directory->GetDirectoryEntries(getter_AddRefs(directoryEntries));
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_GetDirEntries);
return rv;
}
if (!directoryEntries) {
return NS_OK;
}
while (true) {
if (aCanceled) {
break;
}
nsCOMPtr<nsIFile> file;
rv = directoryEntries->GetNextFile(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_GetNextFile);
return rv;
}
if (!file) {
break;
}
nsString leafName;
rv = file->GetLeafName(leafName);
if (NS_WARN_IF(NS_FAILED(rv))) {
REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, 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(kQuotaExternalError, 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.
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
uint64_t usage;
if (quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT, aGroup, aOrigin,
Client::LS, usage)) {
aUsageInfo->AppendToDatabaseUsage(Some(usage));
}
return NS_OK;
}
nsresult QuotaClient::AboutToClearOrigins(
const Nullable<PersistenceType>& 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> 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<mozIStorageConnection> 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<mozIStorageFunction> function(
new MatchFunction(archivedOriginScope->GetPattern()));
rv = connection->CreateFunction(NS_LITERAL_CSTRING("match"), 2, function);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
nsCOMPtr<mozIStorageStatement> 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<nsIFile> 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();
}
void QuotaClient::ReleaseIOThreadObjects() {
AssertIsOnIOThread();
// 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 (aOrigin.IsVoid()) {
RequestAllowToCloseIf([](const Database* const) { return true; });
} else {
RequestAllowToCloseIf([&aOrigin](const Database* const aDatabase) {
return aDatabase->Origin() == aOrigin;
});
}
}
void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) {
AssertIsOnBackgroundThread();
RequestAllowToCloseIf([aContentParentId](const Database* const aDatabase) {
return aDatabase->IsOwnedByProcess(aContentParentId);
});
}
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;
}
RequestAllowToCloseIf([](const Database* const) { return true; });
if (gPreparedObsevers) {
gPreparedObsevers->Clear();
gPreparedObsevers = nullptr;
}
nsCOMPtr<nsITimer> timer = NS_NewTimer();
MOZ_ALWAYS_SUCCEEDS(timer->InitWithNamedFuncCallback(
[](nsITimer* aTimer, void* aClosure) {
ForceKillDatabases();
MOZ_ALWAYS_SUCCEEDS(aTimer->InitWithNamedFuncCallback(
[](nsITimer* aTimer, void* aClosure) {
auto quotaClient = static_cast<QuotaClient*>(aClosure);
quotaClient->ShutdownTimedOut();
},
aClosure, SHUTDOWN_FORCE_CRASH_TIMEOUT_MS, nsITimer::TYPE_ONE_SHOT,
"localstorage::QuotaClient::ShutdownWorkThreads::ForceCrashTimer"));
},
this, SHUTDOWN_FORCE_KILL_TIMEOUT_MS, nsITimer::TYPE_ONE_SHOT,
"localstorage::QuotaClient::ShutdownWorkThreads::ForceKillTimer"));
// 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;
}));
MOZ_ALWAYS_SUCCEEDS(timer->Cancel());
// And finally, shutdown the connection thread.
if (gConnectionThread) {
gConnectionThread->Shutdown();
gConnectionThread = nullptr;
}
}
void QuotaClient::ShutdownTimedOut() {
AssertIsOnBackgroundThread();
MOZ_DIAGNOSTIC_ASSERT(gPrepareDatastoreOps || gDatastores || gLiveDatabases);
nsCString data;
if (gPrepareDatastoreOps) {
data.Append("PrepareDatastoreOperations: ");
data.AppendInt(static_cast<uint32_t>(gPrepareDatastoreOps->Length()));
data.Append(" (");
nsTHashtable<nsCStringHashKey> ids;
for (const auto& prepareDatastoreOp : *gPrepareDatastoreOps) {
MOZ_ASSERT(prepareDatastoreOp);
nsCString id;
prepareDatastoreOp->Stringify(id);
ids.PutEntry(id);
}
StringifyTableKeys(ids, data);
data.Append(")\n");
}
if (gDatastores) {
data.Append("Datastores: ");
data.AppendInt(gDatastores->Count());
data.Append(" (");
nsTHashtable<nsCStringHashKey> ids;
for (const auto& entry : *gDatastores) {
MOZ_ASSERT(entry.GetData());
nsCString id;
entry.GetData()->Stringify(id);
ids.PutEntry(id);
}
StringifyTableKeys(ids, data);
data.Append(")\n");
}
if (gLiveDatabases) {
data.Append("LiveDatabases: ");
data.AppendInt(static_cast<uint32_t>(gLiveDatabases->Length()));
data.Append(" (");
nsTHashtable<nsCStringHashKey> ids;
for (const auto& database : *gLiveDatabases) {
MOZ_ASSERT(database);
nsCString id;
database->Stringify(id);
ids.PutEntry(id);
}
StringifyTableKeys(ids, data);
data.Append(")\n");
}
CrashReporter::AnnotateCrashReport(
CrashReporter::Annotation::LocalStorageShutdownTimeout, data);
MOZ_CRASH("LocalStorage shutdown timed out");
}
nsresult QuotaClient::CreateArchivedOriginScope(
const OriginScope& aOriginScope,
nsAutoPtr<ArchivedOriginScope>& aArchivedOriginScope) {
AssertIsOnIOThread();
nsresult rv;
nsAutoPtr<ArchivedOriginScope> archivedOriginScope;
if (aOriginScope.IsOrigin()) {
nsCString spec;
OriginAttributes attrs;
if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOrigin(), spec,
&attrs))) {
return NS_ERROR_FAILURE;
}
ContentPrincipalInfo contentPrincipalInfo;
contentPrincipalInfo.attrs() = attrs;
contentPrincipalInfo.spec() = spec;
PrincipalInfo storagePrincipalInfo(contentPrincipalInfo);
nsCString originAttrSuffix;
nsCString originKey;
rv = GenerateOriginKey2(storagePrincipalInfo, originAttrSuffix, originKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
archivedOriginScope =
ArchivedOriginScope::CreateFromOrigin(originAttrSuffix, originKey);
} else if (aOriginScope.IsPrefix()) {
nsCString spec;
OriginAttributes attrs;
if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOriginNoSuffix(),
spec, &attrs))) {
return NS_ERROR_FAILURE;
}
ContentPrincipalInfo contentPrincipalInfo;
contentPrincipalInfo.attrs() = attrs;
contentPrincipalInfo.spec() = spec;
PrincipalInfo storagePrincipalInfo(contentPrincipalInfo);
nsCString originAttrSuffix;
nsCString originKey;
rv = GenerateOriginKey2(storagePrincipalInfo, originAttrSuffix, originKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
archivedOriginScope = ArchivedOriginScope::CreateFromPrefix(originKey);
} 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<mozIStorageStatement> 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> 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<nsIObserverService> 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<nsIObserverService> 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<nsVariant> outVar(new nsVariant());
rv = outVar->SetAsBool(result);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
outVar.forget(aResult);
return NS_OK;
}
/*******************************************************************************
* AutoWriteTransaction
******************************************************************************/
AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites)
: mConnection(nullptr), mShadowWrites(aShadowWrites) {
AssertIsOnConnectionThread();
MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction);
}
AutoWriteTransaction::~AutoWriteTransaction() {
AssertIsOnConnectionThread();
MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction);
if (mConnection) {
if (NS_FAILED(mConnection->RollbackWriteTransaction())) {
NS_WARNING("Failed to rollback write transaction!");
}
if (mShadowWrites && NS_FAILED(DetachShadowDatabaseAndUnlock())) {
NS_WARNING("Failed to detach shadow database!");
}
}
}
nsresult AutoWriteTransaction::Start(Connection* aConnection) {
AssertIsOnConnectionThread();
MOZ_ASSERT(aConnection);
MOZ_ASSERT(!mConnection);
nsresult rv;
if (mShadowWrites) {
rv = LockAndAttachShadowDatabase(aConnection);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
rv = aConnection->BeginWriteTransaction();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mConnection = aConnection;
return NS_OK;
}
nsresult AutoWriteTransaction::Commit() {
AssertIsOnConnectionThread();
MOZ_ASSERT(mConnection);
nsresult rv = mConnection->CommitWriteTransaction();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (mShadowWrites) {
rv = DetachShadowDatabaseAndUnlock();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
mConnection = nullptr;
return NS_OK;
}
nsresult AutoWriteTransaction::LockAndAttachShadowDatabase(
Connection* aConnection) {
AssertIsOnConnectionThread();
MOZ_ASSERT(aConnection);
MOZ_ASSERT(!mConnection);
MOZ_ASSERT(mShadowDatabaseLock.isNothing());
MOZ_ASSERT(mShadowWrites);
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
nsCOMPtr<mozIStorageConnection> storageConnection =
aConnection->StorageConnection();
MOZ_ASSERT(storageConnection);
mShadowDatabaseLock.emplace(
aConnection->GetQuotaClient()->ShadowDatabaseMutex());
nsresult rv =
AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult AutoWriteTransaction::DetachShadowDatabaseAndUnlock() {
AssertIsOnConnectionThread();
MOZ_ASSERT(mConnection);
MOZ_ASSERT(mShadowDatabaseLock.isSome());
MOZ_ASSERT(mShadowWrites);
nsCOMPtr<mozIStorageConnection> storageConnection =
mConnection->StorageConnection();
MOZ_ASSERT(storageConnection);
nsresult rv = DetachShadowDatabase(storageConnection);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mShadowDatabaseLock.reset();
return NS_OK;
}
} // namespace dom
} // namespace mozilla