Bug 934640 - Implement IDBFactory.databases() to enumerate IndexedDB databases; r=dom-storage-reviewers,asuth

Differential Revision: https://phabricator.services.mozilla.com/D190053
This commit is contained in:
Jan Varga 2024-03-25 10:54:33 +00:00
Родитель bf65876240
Коммит 504e95b43b
7 изменённых файлов: 401 добавлений и 76 удалений

Просмотреть файл

@ -94,6 +94,7 @@
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/IDBCursorBinding.h"
#include "mozilla/dom/IDBFactory.h"
#include "mozilla/dom/IPCBlob.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/IndexedDatabase.h"
@ -2147,6 +2148,14 @@ class Factory final : public PBackgroundIDBFactoryParent,
bool DeallocPBackgroundIDBFactoryRequestParent(
PBackgroundIDBFactoryRequestParent* aActor) override;
mozilla::ipc::IPCResult RecvGetDatabases(
const PersistenceType& aPersistenceType,
const PrincipalInfo& aPrincipalInfo,
GetDatabasesResolver&& aResolve) override;
private:
Maybe<ContentParentId> GetContentParentId() const;
};
class WaitForTransactionsHelper final : public Runnable {
@ -3354,6 +3363,40 @@ class DeleteDatabaseOp::VersionChangeOp final : public DatabaseOperationBase {
NS_DECL_NSIRUNNABLE
};
class GetDatabasesOp final : public FactoryOp {
nsTArray<DatabaseMetadata> mDatabaseMetadataArray;
Factory::GetDatabasesResolver mResolver;
public:
GetDatabasesOp(SafeRefPtr<Factory> aFactory,
const Maybe<ContentParentId>& aContentParentId,
const PersistenceType aPersistenceType,
const PrincipalInfo& aPrincipalInfo,
Factory::GetDatabasesResolver&& aResolver)
: FactoryOp(std::move(aFactory), aContentParentId, aPersistenceType,
aPrincipalInfo, Nothing(), /* aDeleting */ false),
mResolver(std::move(aResolver)) {}
private:
~GetDatabasesOp() override = default;
nsresult DatabasesNotAvailable();
nsresult DatabaseOpen() override;
nsresult DoDatabaseWork() override;
nsresult BeginVersionChange() override;
bool AreActorsAlive() override;
void SendBlockedNotification() override;
nsresult DispatchToWorkThread() override;
void SendResults() override;
};
class VersionChangeTransactionOp : public TransactionDatabaseOperationBase {
public:
void Cleanup() override;
@ -4732,6 +4775,8 @@ class DatabaseLoggingInfo final {
};
class QuotaClient final : public mozilla::dom::quota::Client {
friend class GetDatabasesOp;
static QuotaClient* sInstance;
nsCOMPtr<nsIEventTarget> mBackgroundThread;
@ -4865,8 +4910,9 @@ class QuotaClient final : public mozilla::dom::quota::Client {
// checks those unfinished deletion and clean them up after that.
template <ObsoleteFilenamesHandling ObsoleteFilenames =
ObsoleteFilenamesHandling::Omit>
Result<GetDatabaseFilenamesResult<ObsoleteFilenames>, nsresult>
GetDatabaseFilenames(nsIFile& aDirectory, const AtomicBool& aCanceled);
Result<GetDatabaseFilenamesResult<ObsoleteFilenames>,
nsresult> static GetDatabaseFilenames(nsIFile& aDirectory,
const AtomicBool& aCanceled);
nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
@ -9172,18 +9218,7 @@ Factory::AllocPBackgroundIDBFactoryRequestParent(
return nullptr;
}
Maybe<ContentParentId> contentParentId;
uint64_t childID = BackgroundParent::GetChildID(Manager());
if (childID) {
// If childID is not zero we are dealing with an other-process actor. We
// want to initialize OpenDatabaseOp/DeleteDatabaseOp here with the ID
// (and later also Database) in that case, so Database::IsOwnedByProcess
// can find Databases belonging to a particular content process when
// QuotaClient::AbortOperationsForProcess is called which is currently used
// to abort operations for content processes only.
contentParentId = Some(ContentParentId(childID));
}
Maybe<ContentParentId> contentParentId = GetContentParentId();
auto actor = [&]() -> RefPtr<FactoryRequestOp> {
if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
@ -9229,6 +9264,64 @@ bool Factory::DeallocPBackgroundIDBFactoryRequestParent(
return true;
}
mozilla::ipc::IPCResult Factory::RecvGetDatabases(
const PersistenceType& aPersistenceType,
const PrincipalInfo& aPrincipalInfo, GetDatabasesResolver&& aResolve) {
AssertIsOnBackgroundThread();
auto ResolveGetDatabasesAndReturn = [&aResolve](const nsresult rv) {
aResolve(rv);
return IPC_OK();
};
QM_TRY(MOZ_TO_RESULT(!QuotaClient::IsShuttingDownOnBackgroundThread()),
ResolveGetDatabasesAndReturn);
QM_TRY(MOZ_TO_RESULT(IsValidPersistenceType(aPersistenceType)),
QM_IPC_FAIL(this));
QM_TRY(MOZ_TO_RESULT(QuotaManager::IsPrincipalInfoValid(aPrincipalInfo)),
QM_IPC_FAIL(this));
MOZ_ASSERT(aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
aPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
PersistenceType persistenceType =
IDBFactory::GetPersistenceType(aPrincipalInfo);
QM_TRY(MOZ_TO_RESULT(aPersistenceType == persistenceType), QM_IPC_FAIL(this));
Maybe<ContentParentId> contentParentId = GetContentParentId();
auto op = MakeRefPtr<GetDatabasesOp>(SafeRefPtrFromThis(), contentParentId,
aPersistenceType, aPrincipalInfo,
std::move(aResolve));
gFactoryOps->AppendElement(op);
// Balanced in CleanupMetadata() which is/must always called by SendResults().
IncreaseBusyCount();
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(op));
return IPC_OK();
}
Maybe<ContentParentId> Factory::GetContentParentId() const {
uint64_t childID = BackgroundParent::GetChildID(Manager());
if (childID) {
// If childID is not zero we are dealing with an other-process actor. We
// want to initialize OpenDatabaseOp/DeleteDatabaseOp here with the ID
// (and later also Database) in that case, so Database::IsOwnedByProcess
// can find Databases belonging to a particular content process when
// QuotaClient::AbortOperationsForProcess is called which is currently used
// to abort operations for content processes only.
return Some(ContentParentId(childID));
}
return Nothing();
}
/*******************************************************************************
* WaitForTransactionsHelper
******************************************************************************/
@ -16626,6 +16719,202 @@ nsresult DeleteDatabaseOp::VersionChangeOp::Run() {
return NS_OK;
}
nsresult GetDatabasesOp::DatabasesNotAvailable() {
AssertIsOnIOThread();
MOZ_ASSERT(mState == State::DatabaseWorkOpen);
mState = State::SendingResults;
QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
return NS_OK;
}
nsresult GetDatabasesOp::DatabaseOpen() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::DatabaseOpenPending);
nsresult rv = SendToIOThread();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult GetDatabasesOp::DoDatabaseWork() {
AssertIsOnIOThread();
MOZ_ASSERT(mState == State::DatabaseWorkOpen);
AUTO_PROFILER_LABEL("GetDatabasesOp::DoDatabaseWork", DOM);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
!OperationMayProceed()) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
QuotaManager* const quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
if (mPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
QM_TRY(MOZ_TO_RESULT(
quotaManager->EnsureTemporaryStorageIsInitializedInternal()));
}
{
QM_TRY_INSPECT(const bool& exists,
quotaManager->DoesOriginDirectoryExist(mOriginMetadata));
if (!exists) {
return DatabasesNotAvailable();
}
}
QM_TRY(([&quotaManager, this]()
-> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
if (mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
QM_TRY_RETURN(
quotaManager->EnsurePersistentOriginIsInitialized(mOriginMetadata));
}
QM_TRY_RETURN(quotaManager->EnsureTemporaryOriginIsInitialized(
mPersistenceType, mOriginMetadata));
}()
.map([](const auto& res) { return Ok{}; })));
{
QM_TRY_INSPECT(const bool& exists,
quotaManager->DoesClientDirectoryExist(
ClientMetadata{mOriginMetadata, Client::IDB}));
if (!exists) {
return DatabasesNotAvailable();
}
}
QM_TRY_INSPECT(
const auto& clientDirectory,
([&quotaManager, this]()
-> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
if (mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
QM_TRY_RETURN(quotaManager->EnsurePersistentClientIsInitialized(
ClientMetadata{mOriginMetadata, Client::IDB}));
}
QM_TRY_RETURN(quotaManager->EnsureTemporaryClientIsInitialized(
ClientMetadata{mOriginMetadata, Client::IDB}));
}()
.map([](const auto& res) { return res.first; })));
QM_TRY_INSPECT(
(const auto& [subdirsToProcess, databaseFilenames]),
QuotaClient::GetDatabaseFilenames(*clientDirectory,
/* aCanceled */ Atomic<bool>{false}));
for (const auto& databaseFilename : databaseFilenames) {
QM_TRY_INSPECT(
const auto& databaseFile,
CloneFileAndAppend(*clientDirectory, databaseFilename + kSQLiteSuffix));
nsString path;
databaseFile->GetPath(path);
IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get();
MOZ_ASSERT(idm);
// If the database is already open then there will be a DatabaseFileManager
// which can provide us with the database name and version without needing
// to open the SQLite database. (Also, we are not allowed to open the
// database on this thread if it's already open.)
SafeRefPtr<DatabaseFileManager> fileManager =
idm->GetFileManagerByDatabaseFilePath(mPersistenceType,
mOriginMetadata.mOrigin, path);
if (fileManager) {
mDatabaseMetadataArray.AppendElement(
DatabaseMetadata(nsString(fileManager->DatabaseName()),
fileManager->DatabaseVersion(), mPersistenceType));
continue;
}
// Since the database is not already open, it is safe and necessary for us
// to open the database on this thread and retrieve its name and version.
// We do not need to worry about racing a database open because database
// opens can only be processed on this thread and we are performing the
// steps below synchronously.
QM_TRY_INSPECT(
const auto& fmDirectory,
CloneFileAndAppend(*clientDirectory,
databaseFilename + kFileManagerDirectoryNameSuffix));
QM_TRY_UNWRAP(
const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
CreateStorageConnection(*databaseFile, *fmDirectory, VoidString(),
mOriginMetadata.mOrigin, mDirectoryLockId,
TelemetryIdForFile(databaseFile), Nothing{}));
{
// Load version information.
QM_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement<
SingleStepResult::ReturnNullIfNoResult>(
*connection, "SELECT name, version FROM database"_ns));
QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED);
QM_TRY_INSPECT(
const auto& databaseName,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, stmt, GetString, 0));
QM_TRY_INSPECT(const int64_t& version,
MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1));
mDatabaseMetadataArray.AppendElement(
DatabaseMetadata(databaseName, version, mPersistenceType));
}
}
mState = State::SendingResults;
QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
return NS_OK;
}
nsresult GetDatabasesOp::BeginVersionChange() {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
bool GetDatabasesOp::AreActorsAlive() {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
void GetDatabasesOp::SendBlockedNotification() {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
nsresult GetDatabasesOp::DispatchToWorkThread() {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
void GetDatabasesOp::SendResults() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::SendingResults);
#ifdef DEBUG
NoteActorDestroyed();
#endif
mResolver(mDatabaseMetadataArray);
mDirectoryLock = nullptr;
CleanupMetadata();
FinishSendResults();
}
TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId)
: DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),

Просмотреть файл

@ -16,8 +16,8 @@
#include "mozilla/dom/Document.h"
#include "mozilla/dom/IDBFactoryBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/ipc/BackgroundChild.h"
@ -53,30 +53,6 @@ using namespace mozilla::ipc;
namespace {
PersistenceType GetPersistenceType(const PrincipalInfo& aPrincipalInfo) {
if (aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
// Chrome privilege always gets persistent storage.
return PERSISTENCE_TYPE_PERSISTENT;
}
if (aPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo) {
nsCString origin =
aPrincipalInfo.get_ContentPrincipalInfo().originNoSuffix();
if (QuotaManager::IsOriginInternal(origin)) {
// Internal origins always get persistent storage.
return PERSISTENCE_TYPE_PERSISTENT;
}
if (aPrincipalInfo.get_ContentPrincipalInfo().attrs().mPrivateBrowsingId >
0) {
return PERSISTENCE_TYPE_PRIVATE;
}
}
return PERSISTENCE_TYPE_DEFAULT;
}
Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT IdentifyPrincipalType(
const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
switch (aPrincipalInfo.type()) {
@ -403,6 +379,32 @@ bool IDBFactory::AllowedForPrincipal(nsIPrincipal* aPrincipal,
return !aPrincipal->GetIsNullPrincipal();
}
// static
PersistenceType IDBFactory::GetPersistenceType(
const PrincipalInfo& aPrincipalInfo) {
if (aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
// Chrome privilege always gets persistent storage.
return PERSISTENCE_TYPE_PERSISTENT;
}
if (aPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo) {
nsCString origin =
aPrincipalInfo.get_ContentPrincipalInfo().originNoSuffix();
if (QuotaManager::IsOriginInternal(origin)) {
// Internal origins always get persistent storage.
return PERSISTENCE_TYPE_PERSISTENT;
}
if (aPrincipalInfo.get_ContentPrincipalInfo().attrs().mPrivateBrowsingId >
0) {
return PERSISTENCE_TYPE_PRIVATE;
}
}
return PERSISTENCE_TYPE_DEFAULT;
}
void IDBFactory::UpdateActiveTransactionCount(int32_t aDelta) {
AssertIsOnOwningThread();
MOZ_DIAGNOSTIC_ASSERT(aDelta > 0 || (mActiveTransactionCount + aDelta) <
@ -471,9 +473,64 @@ RefPtr<IDBOpenDBRequest> IDBFactory::DeleteDatabase(
already_AddRefed<Promise> IDBFactory::Databases(JSContext* const aCx) {
RefPtr<Promise> promise = Promise::CreateInfallible(GetOwnerGlobal());
Sequence<IDBDatabaseInfo> databaseInfos;
// Nothing can be done here if we have previously failed to create a
// background actor.
if (mBackgroundActorFailed) {
promise->MaybeReject(NS_ERROR_FAILURE);
return promise.forget();
}
promise->MaybeResolve(databaseInfos);
PersistenceType persistenceType = GetPersistenceType(*mPrincipalInfo);
QM_TRY(MOZ_TO_RESULT(EnsureBackgroundActor()), [&promise](const nsresult rv) {
promise->MaybeReject(rv);
return promise.forget();
});
mBackgroundActor->SendGetDatabases(persistenceType, *mPrincipalInfo)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](const PBackgroundIDBFactoryChild::GetDatabasesPromise::
ResolveOrRejectValue& aValue) {
if (aValue.IsReject()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
const GetDatabasesResponse& response = aValue.ResolveValue();
switch (response.type()) {
case GetDatabasesResponse::Tnsresult:
promise->MaybeReject(response.get_nsresult());
break;
case GetDatabasesResponse::TArrayOfDatabaseMetadata: {
const auto& array = response.get_ArrayOfDatabaseMetadata();
Sequence<IDBDatabaseInfo> databaseInfos;
for (const auto& databaseMetadata : array) {
IDBDatabaseInfo databaseInfo;
databaseInfo.mName.Construct(databaseMetadata.name());
databaseInfo.mVersion.Construct(databaseMetadata.version());
if (!databaseInfos.AppendElement(std::move(databaseInfo),
fallible)) {
promise->MaybeRejectWithTypeError("Out of memory");
return;
}
}
promise->MaybeResolve(databaseInfos);
break;
}
default:
MOZ_CRASH("Unknown response type!");
}
});
return promise.forget();
}

Просмотреть файл

@ -9,6 +9,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/GlobalTeardownObserver.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
@ -97,6 +98,9 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache {
static bool AllowedForPrincipal(nsIPrincipal* aPrincipal,
bool* aIsSystemPrincipal = nullptr);
static quota::PersistenceType GetPersistenceType(
const PrincipalInfo& aPrincipalInfo);
void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(IDBFactory); }
nsISerialEventTarget* EventTarget() const {

Просмотреть файл

@ -40,6 +40,12 @@ union FactoryRequestParams
DeleteDatabaseRequestParams;
};
union GetDatabasesResponse
{
nsresult;
DatabaseMetadata[];
};
[ChildImpl="indexedDB::BackgroundFactoryChild", ParentImpl=virtual]
sync protocol PBackgroundIDBFactory
{
@ -53,6 +59,10 @@ parent:
async PBackgroundIDBFactoryRequest(FactoryRequestParams params);
async GetDatabases(PersistenceType persistenceType,
PrincipalInfo principalInfo)
returns(GetDatabasesResponse response);
child:
async __delete__();

Просмотреть файл

@ -1,13 +0,0 @@
[database-names-by-origin.html]
expected: OK
[open database names don't leak to cross-origin iframe]
expected: FAIL
[open database names don't leak to cross-origin window]
expected: FAIL
[closed database names don't leak to cross-origin iframe]
expected: FAIL
[closed database names don't leak to cross-origin window]
expected: FAIL

Просмотреть файл

@ -1,16 +1,6 @@
[get-databases.any.html]
expected:
if (processor == "x86") and not debug: [OK, TIMEOUT]
[Enumerate multiple databases.]
expected: FAIL
[Enumerate one database.]
expected: FAIL
[Make sure an empty list is returned for the case of no databases.]
expected:
if (processor == "x86") and not debug: [FAIL, TIMEOUT]
PASS
[Ensure that databases() doesn't pick up changes that haven't commited.]
expected:
@ -21,16 +11,6 @@
[get-databases.any.worker.html]
expected:
if (processor == "x86") and not debug: [OK, TIMEOUT]
[Enumerate multiple databases.]
expected: FAIL
[Enumerate one database.]
expected: FAIL
[Make sure an empty list is returned for the case of no databases.]
expected:
if (processor == "x86") and not debug: [FAIL, TIMEOUT]
PASS
[Ensure that databases() doesn't pick up changes that haven't commited.]
expected:

Просмотреть файл

@ -1,5 +1,3 @@
[idbfactory-origin-isolation.html]
expected:
if (os == "android") and fission: [TIMEOUT, OK]
[Test to make sure that origins have separate locking schemes]
expected: FAIL