diff --git a/dom/quota/ActorsChild.cpp b/dom/quota/ActorsChild.cpp index 7efab9087084..63c32eda3fa6 100644 --- a/dom/quota/ActorsChild.cpp +++ b/dom/quota/ActorsChild.cpp @@ -361,7 +361,6 @@ mozilla::ipc::IPCResult QuotaRequestChild::Recv__delete__( aResponse.get_TemporaryStorageInitializedResponse().initialized()); break; - case RequestResponse::TInitResponse: case RequestResponse::TInitTemporaryStorageResponse: case RequestResponse::TClearOriginResponse: case RequestResponse::TResetOriginResponse: diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp index 77da51f6b9e6..416b41443efe 100644 --- a/dom/quota/ActorsParent.cpp +++ b/dom/quota/ActorsParent.cpp @@ -1656,6 +1656,8 @@ QuotaManager::QuotaManager(const nsAString& aBasePath, mStorageName(aStorageName), mTemporaryStorageUsage(0), mNextDirectoryLockId(0), + mShutdownStorageOpCount(0), + mStorageInitialized(false), mTemporaryStorageInitialized(false), mCacheUsable(false) { AssertIsOnOwningThread(); @@ -4847,6 +4849,36 @@ Result QuotaManager::CreateEmptyLocalStorageArchive( return Ok{}; } +RefPtr QuotaManager::InitializeStorage() { + AssertIsOnOwningThread(); + + // If storage is initialized but there's a clear storage or shutdown storage + // operation already scheduled, we can't immediately resolve the promise and + // return from the function because the clear or shutdown storage operation + // uninitializes storage. + if (mStorageInitialized && !mShutdownStorageOpCount) { + return BoolPromise::CreateAndResolve(true, __func__); + } + + auto initializeStorageOp = CreateInitOp(); + + RegisterNormalOriginOp(*initializeStorageOp); + + initializeStorageOp->RunImmediately(); + + return initializeStorageOp->OnResults()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { + if (aValue.IsReject()) { + return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); + } + + self->mStorageInitialized = true; + + return BoolPromise::CreateAndResolve(true, __func__); + }); +} + nsresult QuotaManager::EnsureStorageIsInitializedInternal() { DiagnosticAssertIsOnIOThread(); @@ -5122,7 +5154,23 @@ RefPtr QuotaManager::ClearStorage() { clearStorageOp->RunImmediately(); - return clearStorageOp->OnResults(); + // Storage clearing also shuts it down, so we need to increses the counter + // here as well. + mShutdownStorageOpCount++; + + return clearStorageOp->OnResults()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { + self->mShutdownStorageOpCount--; + + if (aValue.IsReject()) { + return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); + } + + self->mStorageInitialized = false; + + return BoolPromise::CreateAndResolve(true, __func__); + }); } RefPtr QuotaManager::ShutdownStorage() { @@ -5134,7 +5182,21 @@ RefPtr QuotaManager::ShutdownStorage() { shutdownStorageOp->RunImmediately(); - return shutdownStorageOp->OnResults(); + mShutdownStorageOpCount++; + + return shutdownStorageOp->OnResults()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { + self->mShutdownStorageOpCount--; + + if (aValue.IsReject()) { + return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); + } + + self->mStorageInitialized = false; + + return BoolPromise::CreateAndResolve(true, __func__); + }); } void QuotaManager::ShutdownStorageInternal() { diff --git a/dom/quota/OriginOperations.cpp b/dom/quota/OriginOperations.cpp index 6cae66f993a0..b22cc4142811 100644 --- a/dom/quota/OriginOperations.cpp +++ b/dom/quota/OriginOperations.cpp @@ -289,7 +289,7 @@ class TemporaryStorageInitializedOp final : public InitializedRequestBase { void GetResponse(RequestResponse& aResponse) override; }; -class InitOp final : public QuotaRequestBase { +class InitOp final : public ResolvableNormalOriginOp { public: InitOp(); @@ -298,7 +298,7 @@ class InitOp final : public QuotaRequestBase { nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override; - void GetResponse(RequestResponse& aResponse) override; + bool GetResolveValue() override; }; class InitTemporaryStorageOp final : public QuotaRequestBase { @@ -571,7 +571,9 @@ RefPtr CreateTemporaryStorageInitializedOp() { return MakeRefPtr(); } -RefPtr CreateInitOp() { return MakeRefPtr(); } +RefPtr> CreateInitOp() { + return MakeRefPtr(); +} RefPtr CreateInitTemporaryStorageOp() { return MakeRefPtr(); @@ -1102,7 +1104,10 @@ void TemporaryStorageInitializedOp::GetResponse(RequestResponse& aResponse) { } InitOp::InitOp() - : QuotaRequestBase("dom::quota::InitOp", /* aExclusive */ false) { + : ResolvableNormalOriginOp( + "dom::quota::InitOp", Nullable(), + OriginScope::FromNull(), Nullable(), + /* aExclusive */ false) { AssertIsOnOwningThread(); } @@ -1116,11 +1121,7 @@ nsresult InitOp::DoDirectoryWork(QuotaManager& aQuotaManager) { return NS_OK; } -void InitOp::GetResponse(RequestResponse& aResponse) { - AssertIsOnOwningThread(); - - aResponse = InitResponse(); -} +bool InitOp::GetResolveValue() { return true; } InitTemporaryStorageOp::InitTemporaryStorageOp() : QuotaRequestBase("dom::quota::InitTemporaryStorageOp", diff --git a/dom/quota/OriginOperations.h b/dom/quota/OriginOperations.h index 5e29e547279b..586a1a07a13e 100644 --- a/dom/quota/OriginOperations.h +++ b/dom/quota/OriginOperations.h @@ -53,7 +53,7 @@ RefPtr CreateStorageInitializedOp(); RefPtr CreateTemporaryStorageInitializedOp(); -RefPtr CreateInitOp(); +RefPtr> CreateInitOp(); RefPtr CreateInitTemporaryStorageOp(); diff --git a/dom/quota/PQuota.ipdl b/dom/quota/PQuota.ipdl index 62830f72cd4b..a1fbc2cf39f7 100644 --- a/dom/quota/PQuota.ipdl +++ b/dom/quota/PQuota.ipdl @@ -49,10 +49,6 @@ struct TemporaryStorageInitializedParams { }; -struct InitParams -{ -}; - struct InitTemporaryStorageParams { }; @@ -140,7 +136,6 @@ union RequestParams StorageNameParams; StorageInitializedParams; TemporaryStorageInitializedParams; - InitParams; InitTemporaryStorageParams; InitializePersistentOriginParams; InitializeTemporaryOriginParams; @@ -169,6 +164,9 @@ parent: async PQuotaRequest(RequestParams params); + async InitializeStorage() + returns(BoolResponse response); + async ClearStoragesForPrivateBrowsing() returns(BoolResponse response); diff --git a/dom/quota/PQuotaRequest.ipdl b/dom/quota/PQuotaRequest.ipdl index d658836ec37b..be8ba4925611 100644 --- a/dom/quota/PQuotaRequest.ipdl +++ b/dom/quota/PQuotaRequest.ipdl @@ -28,10 +28,6 @@ struct TemporaryStorageInitializedResponse bool initialized; }; -struct InitResponse -{ -}; - struct InitTemporaryStorageResponse { }; @@ -89,7 +85,6 @@ union RequestResponse StorageNameResponse; StorageInitializedResponse; TemporaryStorageInitializedResponse; - InitResponse; InitTemporaryStorageResponse; InitializePersistentOriginResponse; InitializeTemporaryOriginResponse; diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h index d9b3297089de..647b5f51240c 100644 --- a/dom/quota/QuotaManager.h +++ b/dom/quota/QuotaManager.h @@ -300,6 +300,14 @@ class QuotaManager final : public BackgroundThreadObject { template void CollectPendingOriginsForListing(P aPredicate); + RefPtr InitializeStorage(); + + bool IsStorageInitialized() const { + AssertIsOnOwningThread(); + + return mStorageInitialized; + } + bool IsStorageInitializedInternal() const { AssertIsOnIOThread(); return static_cast(mStorageConnection); @@ -709,6 +717,8 @@ class QuotaManager final : public BackgroundThreadObject { uint64_t mTemporaryStorageLimit; uint64_t mTemporaryStorageUsage; int64_t mNextDirectoryLockId; + uint64_t mShutdownStorageOpCount; + bool mStorageInitialized; bool mTemporaryStorageInitialized; bool mCacheUsable; }; diff --git a/dom/quota/QuotaManagerService.cpp b/dom/quota/QuotaManagerService.cpp index c1539e96a485..e4a5689e3ef1 100644 --- a/dom/quota/QuotaManagerService.cpp +++ b/dom/quota/QuotaManagerService.cpp @@ -528,16 +528,13 @@ QuotaManagerService::Init(nsIQuotaRequest** _retval) { return NS_ERROR_UNEXPECTED; } + QM_TRY(MOZ_TO_RESULT(EnsureBackgroundActor())); + RefPtr request = new Request(); - InitParams params; - - RequestInfo info(request, params); - - nsresult rv = InitiateRequest(info); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } + mBackgroundActor->SendInitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + BoolResponsePromiseResolveOrRejectCallback(request)); request.forget(_retval); return NS_OK; diff --git a/dom/quota/QuotaParent.cpp b/dom/quota/QuotaParent.cpp index 2d92f1f4a526..a0122c8b02de 100644 --- a/dom/quota/QuotaParent.cpp +++ b/dom/quota/QuotaParent.cpp @@ -134,7 +134,6 @@ bool Quota::VerifyRequestParams(const RequestParams& aParams) const { case RequestParams::TStorageNameParams: case RequestParams::TStorageInitializedParams: case RequestParams::TTemporaryStorageInitializedParams: - case RequestParams::TInitParams: case RequestParams::TInitTemporaryStorageParams: break; @@ -408,9 +407,6 @@ PQuotaRequestParent* Quota::AllocPQuotaRequestParent( case RequestParams::TTemporaryStorageInitializedParams: return CreateTemporaryStorageInitializedOp(); - case RequestParams::TInitParams: - return CreateInitOp(); - case RequestParams::TInitTemporaryStorageParams: return CreateInitTemporaryStorageOp(); @@ -481,6 +477,24 @@ bool Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) { return true; } +mozilla::ipc::IPCResult Quota::RecvInitializeStorage( + InitializeStorageResolver&& aResolver) { + AssertIsOnBackgroundThread(); + + QM_TRY(MOZ_TO_RESULT(!QuotaManager::IsShuttingDown()), + ResolveBoolResponseAndReturn(aResolver)); + + QM_TRY_UNWRAP(const NotNull> quotaManager, + QuotaManager::GetOrCreate(), + ResolveBoolResponseAndReturn(aResolver)); + + quotaManager->InitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + BoolPromiseResolveOrRejectCallback(this, std::move(aResolver))); + + return IPC_OK(); +} + mozilla::ipc::IPCResult Quota::RecvClearStoragesForPrivateBrowsing( ClearStoragesForPrivateBrowsingResolver&& aResolver) { AssertIsOnBackgroundThread(); diff --git a/dom/quota/QuotaParent.h b/dom/quota/QuotaParent.h index 45bbd8fd4ea8..c4aa74f09302 100644 --- a/dom/quota/QuotaParent.h +++ b/dom/quota/QuotaParent.h @@ -50,6 +50,9 @@ class Quota final : public PQuotaParent { virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override; + virtual mozilla::ipc::IPCResult RecvInitializeStorage( + InitializeStorageResolver&& aResolver) override; + virtual mozilla::ipc::IPCResult RecvClearStoragesForPrivateBrowsing( ClearStoragesForPrivateBrowsingResolver&& aResolver) override; diff --git a/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp b/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp index 5bfa7f802746..869475d664a6 100644 --- a/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp +++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp @@ -92,6 +92,24 @@ void QuotaManagerDependencyFixture::ShutdownFixture() { sBackgroundTarget = nullptr; } +// static +void QuotaManagerDependencyFixture::InitializeStorage() { + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + bool done = false; + + quotaManager->InitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& aValue) { + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); +} + // static void QuotaManagerDependencyFixture::StorageInitialized(bool* aResult) { AutoJSAPI jsapi; @@ -124,6 +142,32 @@ void QuotaManagerDependencyFixture::StorageInitialized(bool* aResult) { } } +// static +void QuotaManagerDependencyFixture::IsStorageInitialized(bool* aResult) { + ASSERT_TRUE(aResult); + + PerformOnBackgroundThread([aResult]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + *aResult = quotaManager->IsStorageInitialized(); + }); +} + +// static +void QuotaManagerDependencyFixture::AssertStorageIsInitialized() { + bool result; + ASSERT_NO_FATAL_FAILURE(IsStorageInitialized(&result)); + ASSERT_TRUE(result); +} + +// static +void QuotaManagerDependencyFixture::AssertStorageIsNotInitialized() { + bool result; + ASSERT_NO_FATAL_FAILURE(IsStorageInitialized(&result)); + ASSERT_FALSE(result); +} + // static void QuotaManagerDependencyFixture::ShutdownStorage() { PerformOnBackgroundThread([]() { @@ -173,6 +217,21 @@ void QuotaManagerDependencyFixture::ClearStoragesForOrigin( [&resolver]() { return resolver->Done(); }); } +// static +OriginMetadata QuotaManagerDependencyFixture::GetTestOriginMetadata() { + return {""_ns, + "example.com"_ns, + "http://example.com"_ns, + "http://example.com"_ns, + /* aIsPrivate */ false, + PERSISTENCE_TYPE_DEFAULT}; +} + +// static +ClientMetadata QuotaManagerDependencyFixture::GetTestClientMetadata() { + return {GetTestOriginMetadata(), Client::SDB}; +} + nsCOMPtr QuotaManagerDependencyFixture::sBackgroundTarget; } // namespace mozilla::dom::quota::test diff --git a/dom/quota/test/gtest/QuotaManagerDependencyFixture.h b/dom/quota/test/gtest/QuotaManagerDependencyFixture.h index 5686159e480e..dcf319df8d7d 100644 --- a/dom/quota/test/gtest/QuotaManagerDependencyFixture.h +++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.h @@ -22,7 +22,11 @@ class QuotaManagerDependencyFixture : public testing::Test { static void ShutdownFixture(); + static void InitializeStorage(); static void StorageInitialized(bool* aResult = nullptr); + static void IsStorageInitialized(bool* aResult); + static void AssertStorageIsInitialized(); + static void AssertStorageIsNotInitialized(); static void ShutdownStorage(); static void ClearStoragesForOrigin(const OriginMetadata& aOriginMetadata); @@ -79,6 +83,9 @@ class QuotaManagerDependencyFixture : public testing::Test { return sBackgroundTarget; } + static OriginMetadata GetTestOriginMetadata(); + static ClientMetadata GetTestClientMetadata(); + private: static nsCOMPtr sBackgroundTarget; }; diff --git a/dom/quota/test/gtest/TestQuotaManager.cpp b/dom/quota/test/gtest/TestQuotaManager.cpp index bc54b3fc099f..74d6b323a288 100644 --- a/dom/quota/test/gtest/TestQuotaManager.cpp +++ b/dom/quota/test/gtest/TestQuotaManager.cpp @@ -6,6 +6,8 @@ #include "gtest/gtest.h" #include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/dom/quota/DirectoryLock.h" +#include "mozilla/dom/quota/OriginScope.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/gtest/MozAssertions.h" #include "QuotaManagerDependencyFixture.h" @@ -19,29 +21,611 @@ class TestQuotaManager : public QuotaManagerDependencyFixture { static void TearDownTestCase() { ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); } }; +// Test simple InitializeStorage. +TEST_F(TestQuotaManager, InitializeStorage_Simple) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + bool done = false; + + quotaManager->InitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& aValue) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization is already ongoing. +TEST_F(TestQuotaManager, InitializeStorage_Ongoing) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + nsTArray> promises; + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(quotaManager->InitializeStorage()); + + bool done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization is already ongoing and +// storage shutdown is scheduled after that. +TEST_F(TestQuotaManager, InitializeStorage_OngoingWithScheduledShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + nsTArray> promises; + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(quotaManager->ShutdownStorage()); + promises.AppendElement(quotaManager->InitializeStorage()); + + bool done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization is already ongoing and +// an exclusive directory lock is requested after that. +TEST_F(TestQuotaManager, InitializeStorage_OngoingWithExclusiveDirectoryLock) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + RefPtr directoryLock = + quotaManager->CreateDirectoryLockInternal(Nullable(), + OriginScope::FromNull(), + Nullable(), + /* aExclusive */ true); + + nsTArray> promises; + + promises.AppendElement(quotaManager->InitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) { + // The exclusive directory lock must be released when the first + // storage initialization is finished, otherwise it would endlessly + // block the second storage initialization. + directoryLock = nullptr; + + if (aValue.IsReject()) { + return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); + } + + return BoolPromise::CreateAndResolve(true, __func__); + })); + promises.AppendElement(directoryLock->Acquire()); + promises.AppendElement(quotaManager->InitializeStorage()); + + bool done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization is already ongoing and +// shared client directory locks are requested after that. +// The shared client directory locks don't have to be released in this case. +TEST_F(TestQuotaManager, InitializeStorage_OngoingWithClientDirectoryLocks) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + RefPtr directoryLock = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + RefPtr directoryLock2 = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + nsTArray> promises; + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock->Acquire()); + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock2->Acquire()); + + bool done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization is already ongoing and +// shared client directory locks are requested after that with storage shutdown +// scheduled in between. +TEST_F(TestQuotaManager, + InitializeStorage_OngoingWithClientDirectoryLocksAndScheduledShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + RefPtr directoryLock = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + directoryLock->OnInvalidate( + [&directoryLock]() { directoryLock = nullptr; }); + + RefPtr directoryLock2 = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + nsTArray> promises; + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock->Acquire()); + promises.AppendElement(quotaManager->ShutdownStorage()); + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock2->Acquire()); + + bool done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization already finished. +TEST_F(TestQuotaManager, InitializeStorage_Finished) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + bool done = false; + + quotaManager->InitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& aValue) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + + done = false; + + quotaManager->InitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& aValue) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization already finished but +// storage shutdown has just been scheduled. +TEST_F(TestQuotaManager, InitializeStorage_FinishedWithScheduledShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + bool done = false; + + quotaManager->InitializeStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& aValue) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + + nsTArray> promises; + + promises.AppendElement(quotaManager->ShutdownStorage()); + promises.AppendElement(quotaManager->InitializeStorage()); + + done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization already finished and +// shared client directory locks are requested immediately after requesting +// storage initialization. +TEST_F(TestQuotaManager, InitializeStorage_FinishedWithClientDirectoryLocks) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + RefPtr directoryLock = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + nsTArray> promises; + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock->Acquire()); + + bool done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + + RefPtr directoryLock2 = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + promises.Clear(); + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock2->Acquire()); + + done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + +// Test InitializeStorage when a storage initialization already finished and +// shared client directory locks are requested immediatelly after requesting +// storage initialization with storage shutdown performed in between. +// The shared client directory lock is released when it gets invalidated by +// storage shutdown which then unblocks the shutdown. +TEST_F(TestQuotaManager, + InitializeStorage_FinishedWithClientDirectoryLocksAndScheduledShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + PerformOnBackgroundThread([]() { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + RefPtr directoryLock = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + directoryLock->OnInvalidate( + [&directoryLock]() { directoryLock = nullptr; }); + + nsTArray> promises; + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock->Acquire()); + + bool done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + + done = false; + + quotaManager->ShutdownStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& aValue) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_FALSE(quotaManager->IsStorageInitialized()); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + + RefPtr directoryLock2 = + quotaManager->CreateDirectoryLock(GetTestClientMetadata(), + /* aExclusive */ false); + + promises.Clear(); + + promises.AppendElement(quotaManager->InitializeStorage()); + promises.AppendElement(directoryLock2->Acquire()); + + done = false; + + BoolPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_TRUE(quotaManager->IsStorageInitialized()); + + done = true; + }, + [&done](nsresult aRejectValue) { + ASSERT_TRUE(false); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); +} + // Test simple ShutdownStorage. TEST_F(TestQuotaManager, ShutdownStorage_Simple) { ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); - PerformOnIOThread([]() { + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); + + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); + + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); + + PerformOnBackgroundThread([]() { QuotaManager* quotaManager = QuotaManager::Get(); ASSERT_TRUE(quotaManager); - ASSERT_FALSE(quotaManager->IsStorageInitializedInternal()); + bool done = false; - ASSERT_NS_SUCCEEDED(quotaManager->EnsureStorageIsInitializedInternal()); + quotaManager->ShutdownStorage()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& aValue) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); - ASSERT_TRUE(quotaManager->IsStorageInitializedInternal()); + ASSERT_FALSE(quotaManager->IsStorageInitialized()); + + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); }); - ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); - - PerformOnIOThread([]() { - QuotaManager* quotaManager = QuotaManager::Get(); - ASSERT_TRUE(quotaManager); - - ASSERT_FALSE(quotaManager->IsStorageInitializedInternal()); - }); + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); } @@ -50,16 +634,11 @@ TEST_F(TestQuotaManager, ShutdownStorage_Simple) { TEST_F(TestQuotaManager, ShutdownStorage_Ongoing) { ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); - PerformOnIOThread([]() { - QuotaManager* quotaManager = QuotaManager::Get(); - ASSERT_TRUE(quotaManager); + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); - ASSERT_FALSE(quotaManager->IsStorageInitializedInternal()); + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); - ASSERT_NS_SUCCEEDED(quotaManager->EnsureStorageIsInitializedInternal()); - - ASSERT_TRUE(quotaManager->IsStorageInitializedInternal()); - }); + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); PerformOnBackgroundThread([]() { QuotaManager* quotaManager = QuotaManager::Get(); @@ -76,6 +655,11 @@ TEST_F(TestQuotaManager, ShutdownStorage_Ongoing) { ->Then( GetCurrentSerialEventTarget(), __func__, [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_FALSE(quotaManager->IsStorageInitialized()); + done = true; }, [&done](nsresult aRejectValue) { @@ -87,12 +671,7 @@ TEST_F(TestQuotaManager, ShutdownStorage_Ongoing) { SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); }); - PerformOnIOThread([]() { - QuotaManager* quotaManager = QuotaManager::Get(); - ASSERT_TRUE(quotaManager); - - ASSERT_FALSE(quotaManager->IsStorageInitializedInternal()); - }); + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); } @@ -102,16 +681,11 @@ TEST_F(TestQuotaManager, ShutdownStorage_Ongoing) { TEST_F(TestQuotaManager, ShutdownStorage_OngoingWithScheduledInitialization) { ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); - PerformOnIOThread([]() { - QuotaManager* quotaManager = QuotaManager::Get(); - ASSERT_TRUE(quotaManager); + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); - ASSERT_FALSE(quotaManager->IsStorageInitializedInternal()); + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); - ASSERT_NS_SUCCEEDED(quotaManager->EnsureStorageIsInitializedInternal()); - - ASSERT_TRUE(quotaManager->IsStorageInitializedInternal()); - }); + ASSERT_NO_FATAL_FAILURE(AssertStorageIsInitialized()); PerformOnBackgroundThread([]() { QuotaManager* quotaManager = QuotaManager::Get(); @@ -120,11 +694,7 @@ TEST_F(TestQuotaManager, ShutdownStorage_OngoingWithScheduledInitialization) { nsTArray> promises; promises.AppendElement(quotaManager->ShutdownStorage()); - // XXX We have to use ClearPrivateRepository for now because there's no - // dedicated method for initializing storage on the PBackground thread yet. - // ClearPrivateRepository triggers storage initialization before it clear - // the private repository. - promises.AppendElement(quotaManager->ClearPrivateRepository()); + promises.AppendElement(quotaManager->InitializeStorage()); promises.AppendElement(quotaManager->ShutdownStorage()); bool done = false; @@ -133,6 +703,11 @@ TEST_F(TestQuotaManager, ShutdownStorage_OngoingWithScheduledInitialization) { ->Then( GetCurrentSerialEventTarget(), __func__, [&done](const CopyableTArray& aResolveValues) { + QuotaManager* quotaManager = QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + ASSERT_FALSE(quotaManager->IsStorageInitialized()); + done = true; }, [&done](nsresult aRejectValue) { @@ -144,12 +719,7 @@ TEST_F(TestQuotaManager, ShutdownStorage_OngoingWithScheduledInitialization) { SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); }); - PerformOnIOThread([]() { - QuotaManager* quotaManager = QuotaManager::Get(); - ASSERT_TRUE(quotaManager); - - ASSERT_FALSE(quotaManager->IsStorageInitializedInternal()); - }); + ASSERT_NO_FATAL_FAILURE(AssertStorageIsNotInitialized()); ASSERT_NO_FATAL_FAILURE(ShutdownStorage()); }