diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /dom/indexedDB | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/indexedDB')
36 files changed, 1505 insertions, 435 deletions
diff --git a/dom/indexedDB/ActorsChild.cpp b/dom/indexedDB/ActorsChild.cpp index a6f3ef7789..1a122ea3c1 100644 --- a/dom/indexedDB/ActorsChild.cpp +++ b/dom/indexedDB/ActorsChild.cpp @@ -1429,7 +1429,7 @@ mozilla::ipc::IPCResult BackgroundTransactionChild::RecvComplete( PBackgroundIDBRequestChild* BackgroundTransactionChild::AllocPBackgroundIDBRequestChild( - const RequestParams& aParams) { + const int64_t& aRequestId, const RequestParams& aParams) { MOZ_CRASH( "PBackgroundIDBRequestChild actors should be manually " "constructed!"); @@ -1445,7 +1445,7 @@ bool BackgroundTransactionChild::DeallocPBackgroundIDBRequestChild( PBackgroundIDBCursorChild* BackgroundTransactionChild::AllocPBackgroundIDBCursorChild( - const OpenCursorParams& aParams) { + const int64_t& aRequestId, const OpenCursorParams& aParams) { AssertIsOnOwningThread(); MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!"); @@ -1547,7 +1547,7 @@ mozilla::ipc::IPCResult BackgroundVersionChangeTransactionChild::RecvComplete( PBackgroundIDBRequestChild* BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBRequestChild( - const RequestParams& aParams) { + const int64_t& aRequestId, const RequestParams& aParams) { MOZ_CRASH( "PBackgroundIDBRequestChild actors should be manually " "constructed!"); @@ -1563,7 +1563,7 @@ bool BackgroundVersionChangeTransactionChild::DeallocPBackgroundIDBRequestChild( PBackgroundIDBCursorChild* BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBCursorChild( - const OpenCursorParams& aParams) { + const int64_t& aRequestId, const OpenCursorParams& aParams) { AssertIsOnOwningThread(); MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!"); @@ -2211,7 +2211,7 @@ BackgroundCursorChild<CursorType>::SafeRefPtrFromThis() { template <IDBCursorType CursorType> void BackgroundCursorChild<CursorType>::SendContinueInternal( - const CursorRequestParams& aParams, + const int64_t aRequestId, const CursorRequestParams& aParams, const CursorData<CursorType>& aCurrentData) { AssertIsOnOwningThread(); MOZ_ASSERT(mRequest); @@ -2394,7 +2394,7 @@ void BackgroundCursorChild<CursorType>::SendContinueInternal( // handling model disallow this? } else { MOZ_ALWAYS_TRUE(PBackgroundIDBCursorChild::SendContinue( - params, currentKey, currentObjectStoreKey)); + aRequestId, params, currentKey, currentObjectStoreKey)); } } diff --git a/dom/indexedDB/ActorsChild.h b/dom/indexedDB/ActorsChild.h index e3e204be0b..194ed28878 100644 --- a/dom/indexedDB/ActorsChild.h +++ b/dom/indexedDB/ActorsChild.h @@ -347,12 +347,12 @@ class BackgroundTransactionChild final : public BackgroundTransactionBase, mozilla::ipc::IPCResult RecvComplete(nsresult aResult); PBackgroundIDBRequestChild* AllocPBackgroundIDBRequestChild( - const RequestParams& aParams); + const int64_t& aRequestId, const RequestParams& aParams); bool DeallocPBackgroundIDBRequestChild(PBackgroundIDBRequestChild* aActor); PBackgroundIDBCursorChild* AllocPBackgroundIDBCursorChild( - const OpenCursorParams& aParams); + const int64_t& aRequestId, const OpenCursorParams& aParams); bool DeallocPBackgroundIDBCursorChild(PBackgroundIDBCursorChild* aActor); }; @@ -393,12 +393,12 @@ class BackgroundVersionChangeTransactionChild final mozilla::ipc::IPCResult RecvComplete(nsresult aResult); PBackgroundIDBRequestChild* AllocPBackgroundIDBRequestChild( - const RequestParams& aParams); + const int64_t& aRequestId, const RequestParams& aParams); bool DeallocPBackgroundIDBRequestChild(PBackgroundIDBRequestChild* aActor); PBackgroundIDBCursorChild* AllocPBackgroundIDBCursorChild( - const OpenCursorParams& aParams); + const int64_t& aRequestId, const OpenCursorParams& aParams); bool DeallocPBackgroundIDBCursorChild(PBackgroundIDBCursorChild* aActor); }; @@ -548,7 +548,8 @@ class BackgroundCursorChild final : public BackgroundCursorChildBase { BackgroundCursorChild(NotNull<IDBRequest*> aRequest, SourceType* aSource, Direction aDirection); - void SendContinueInternal(const CursorRequestParams& aParams, + void SendContinueInternal(const int64_t aRequestId, + const CursorRequestParams& aParams, const CursorData<CursorType>& aCurrentData); void InvalidateCachedResponses(); @@ -594,7 +595,8 @@ class BackgroundCursorChild final : public BackgroundCursorChildBase { mozilla::ipc::IPCResult RecvResponse(CursorResponse&& aResponse) override; // Force callers to use SendContinueInternal. - bool SendContinue(const CursorRequestParams& aParams, const Key& aCurrentKey, + bool SendContinue(const int64_t& aRequestId, + const CursorRequestParams& aParams, const Key& aCurrentKey, const Key& aCurrentObjectStoreKey) = delete; bool SendDeleteMe() = delete; diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index 47360094ca..6609ea5b81 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -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" @@ -122,6 +123,7 @@ #include "mozilla/dom/quota/CheckedUnsafePtr.h" #include "mozilla/dom/quota/Client.h" #include "mozilla/dom/quota/ClientImpl.h" +#include "mozilla/dom/quota/DebugOnlyMacro.h" #include "mozilla/dom/quota/DirectoryLock.h" #include "mozilla/dom/quota/DecryptingInputStream_impl.h" #include "mozilla/dom/quota/EncryptingOutputStream_impl.h" @@ -1973,6 +1975,10 @@ class TransactionDatabaseOperationBase : public DatabaseOperationBase { }; InitializedOnce<const NotNull<SafeRefPtr<TransactionBase>>> mTransaction; + // Unique request id within the context of the transaction, allocated by the + // transaction in the content process starting from 0. Values less than 0 are + // impossible and forbidden. Used to support the explicit commit() request. + const int64_t mRequestId; InternalState mInternalState = InternalState::Initial; bool mWaitingForContinue = false; const bool mTransactionIsAborted; @@ -2034,10 +2040,11 @@ class TransactionDatabaseOperationBase : public DatabaseOperationBase { virtual void Cleanup(); protected: - explicit TransactionDatabaseOperationBase( - SafeRefPtr<TransactionBase> aTransaction); + TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction, + int64_t aRequestId); TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, uint64_t aLoggingSerialNumber); ~TransactionDatabaseOperationBase() override; @@ -2141,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 { @@ -2395,11 +2410,13 @@ class Database final already_AddRefed<PBackgroundIDBTransactionParent> AllocPBackgroundIDBTransactionParent( - const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode) override; + const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode, + const Durability& aDurability) override; mozilla::ipc::IPCResult RecvPBackgroundIDBTransactionConstructor( PBackgroundIDBTransactionParent* aActor, - nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode) override; + nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode, + const Durability& aDurability) override; mozilla::ipc::IPCResult RecvDeleteMe() override; @@ -2418,6 +2435,7 @@ class Database::StartTransactionOp final private: explicit StartTransactionOp(SafeRefPtr<TransactionBase> aTransaction) : TransactionDatabaseOperationBase(std::move(aTransaction), + /* aRequestId */ 0, /* aLoggingSerialNumber */ 0) {} ~StartTransactionOp() override = default; @@ -2591,6 +2609,7 @@ class TransactionBase : public AtomicSafeRefCounted<TransactionBase> { protected: using Mode = IDBTransaction::Mode; + using Durability = IDBTransaction::Durability; private: const SafeRefPtr<Database> mDatabase; @@ -2602,6 +2621,7 @@ class TransactionBase : public AtomicSafeRefCounted<TransactionBase> { uint64_t mActiveRequestCount; Atomic<bool> mInvalidatedOnAnyThread; const Mode mMode; + const Durability mDurability; // TODO: See bug 1883045 FlippedOnce<false> mInitialized; FlippedOnce<false> mHasBeenActiveOnConnectionThread; FlippedOnce<false> mActorDestroyed; @@ -2661,6 +2681,8 @@ class TransactionBase : public AtomicSafeRefCounted<TransactionBase> { Mode GetMode() const { return mMode; } + Durability GetDurability() const { return mDurability; } + const Database& GetDatabase() const { MOZ_ASSERT(mDatabase); @@ -2723,7 +2745,8 @@ class TransactionBase : public AtomicSafeRefCounted<TransactionBase> { virtual ~TransactionBase(); protected: - TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode); + TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode, + Durability aDurability); void NoteActorDestroyed() { AssertIsOnBackgroundThread(); @@ -2765,7 +2788,8 @@ class TransactionBase : public AtomicSafeRefCounted<TransactionBase> { CommitOrAbort(); } - PBackgroundIDBRequestParent* AllocRequest(RequestParams&& aParams, + PBackgroundIDBRequestParent* AllocRequest(const int64_t aRequestId, + RequestParams&& aParams, bool aTrustParams); bool StartRequest(PBackgroundIDBRequestParent* aActor); @@ -2775,7 +2799,7 @@ class TransactionBase : public AtomicSafeRefCounted<TransactionBase> { already_AddRefed<PBackgroundIDBCursorParent> AllocCursor( const OpenCursorParams& aParams, bool aTrustParams); - bool StartCursor(PBackgroundIDBCursorParent* aActor, + bool StartCursor(PBackgroundIDBCursorParent* aActor, const int64_t aRequestId, const OpenCursorParams& aParams); virtual void UpdateMetadata(nsresult aResult) {} @@ -2858,26 +2882,27 @@ class NormalTransaction final : public TransactionBase, mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override; PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent( - const RequestParams& aParams) override; + const int64_t& aRequestId, const RequestParams& aParams) override; mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor( - PBackgroundIDBRequestParent* aActor, + PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId, const RequestParams& aParams) override; bool DeallocPBackgroundIDBRequestParent( PBackgroundIDBRequestParent* aActor) override; already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent( - const OpenCursorParams& aParams) override; + const int64_t& aRequestId, const OpenCursorParams& aParams) override; mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor( - PBackgroundIDBCursorParent* aActor, + PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId, const OpenCursorParams& aParams) override; public: // This constructor is only called by Database. NormalTransaction( SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode, + TransactionBase::Durability aDurability, nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores); MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(NormalTransaction, TransactionBase) @@ -2950,26 +2975,25 @@ class VersionChangeTransaction final const IndexOrObjectStoreId& aIndexId, const nsAString& aName) override; PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent( - const RequestParams& aParams) override; + const int64_t& aRequestId, const RequestParams& aParams) override; mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor( - PBackgroundIDBRequestParent* aActor, + PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId, const RequestParams& aParams) override; bool DeallocPBackgroundIDBRequestParent( PBackgroundIDBRequestParent* aActor) override; already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent( - const OpenCursorParams& aParams) override; + const int64_t& aRequestId, const OpenCursorParams& aParams) override; mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor( - PBackgroundIDBCursorParent* aActor, + PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId, const OpenCursorParams& aParams) override; }; class FactoryOp : public DatabaseOperationBase, - public PBackgroundIDBFactoryRequestParent, public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { public: struct MaybeBlockedDatabaseInfo final { @@ -3008,9 +3032,20 @@ class FactoryOp // Waiting for directory open allowed on the PBackground thread. The next // step is either SendingResults if directory lock failed to acquire, or - // DatabaseOpenPending if directory lock is acquired. + // DirectoryWorkOpen if the factory operation is not tied up to a specific + // database, or DatabaseOpenPending otherwise. DirectoryOpenPending, + // Waiting to do/doing directory work on the QuotaManager IO thread. Its + // next step is DirectoryWorkDone if directory work was successful or + // SendingResults if directory work failed. + DirectoryWorkOpen, + + // Checking if database work can be started. If the database is not blocked + // by other factory operations then the next step is DatabaseWorkOpen. + // Otherwise the next step is DatabaseOpenPending. + DirectoryWorkDone, + // Waiting for database open allowed on the PBackground thread. The next // step is DatabaseWorkOpen. DatabaseOpenPending, @@ -3060,14 +3095,18 @@ class FactoryOp // Must be released on the main thread! RefPtr<DirectoryLock> mDirectoryLock; - RefPtr<FactoryOp> mDelayedOp; + nsTArray<NotNull<RefPtr<FactoryOp>>> mBlocking; + nsTArray<NotNull<RefPtr<FactoryOp>>> mBlockedOn; + nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases; - const CommonFactoryRequestParams mCommonParams; + const PrincipalInfo mPrincipalInfo; OriginMetadata mOriginMetadata; - nsCString mDatabaseId; - nsString mDatabaseFilePath; + Maybe<nsString> mDatabaseName; + Maybe<nsCString> mDatabaseId; + Maybe<nsString> mDatabaseFilePath; int64_t mDirectoryLockId; + const PersistenceType mPersistenceType; State mState; bool mWaitingForPermissionRetry; bool mEnforcingQuota; @@ -3081,17 +3120,23 @@ class FactoryOp return mOriginMetadata.mOrigin; } + const Maybe<nsString>& DatabaseNameRef() const { + AssertIsOnOwningThread(); + + return mDatabaseName; + } + bool DatabaseFilePathIsKnown() const { AssertIsOnOwningThread(); - return !mDatabaseFilePath.IsEmpty(); + return mDatabaseFilePath.isSome(); } const nsAString& DatabaseFilePath() const { AssertIsOnOwningThread(); - MOZ_ASSERT(!mDatabaseFilePath.IsEmpty()); + MOZ_ASSERT(mDatabaseFilePath); - return mDatabaseFilePath; + return mDatabaseFilePath.ref(); } void NoteDatabaseBlocked(Database* aDatabase); @@ -3109,7 +3154,9 @@ class FactoryOp protected: FactoryOp(SafeRefPtr<Factory> aFactory, const Maybe<ContentParentId>& aContentParentId, - const CommonFactoryRequestParams& aCommonParams, bool aDeleting); + const PersistenceType aPersistenceType, + const PrincipalInfo& aPrincipalInfo, + const Maybe<nsString>& aDatabaseName, bool aDeleting); ~FactoryOp() override { // Normally this would be out-of-line since it is a virtual function but @@ -3122,6 +3169,8 @@ class FactoryOp nsresult DirectoryOpen(); + nsresult DirectoryWorkDone(); + nsresult SendToIOThread(); void WaitForTransactions(); @@ -3136,6 +3185,8 @@ class FactoryOp const Maybe<uint64_t>& aNewVersion); // Methods that subclasses must implement. + virtual nsresult DoDirectoryWork() = 0; + virtual nsresult DatabaseOpen() = 0; virtual nsresult DoDatabaseWork() = 0; @@ -3157,17 +3208,56 @@ class FactoryOp void DirectoryLockFailed(); - // IPDL methods. - void ActorDestroy(ActorDestroyReason aWhy) override; - virtual void SendBlockedNotification() = 0; private: // Test whether this FactoryOp needs to wait for the given op. bool MustWaitFor(const FactoryOp& aExistingOp); + + void AddBlockingOp(FactoryOp& aOp) { + AssertIsOnOwningThread(); + + mBlocking.AppendElement(WrapNotNull(&aOp)); + } + + void AddBlockedOnOp(FactoryOp& aOp) { + AssertIsOnOwningThread(); + + mBlockedOn.AppendElement(WrapNotNull(&aOp)); + } + + void MaybeUnblock(FactoryOp& aOp) { + AssertIsOnOwningThread(); + + mBlockedOn.RemoveElement(&aOp); + if (mBlockedOn.IsEmpty()) { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this)); + } + } +}; + +class FactoryRequestOp : public FactoryOp, + public PBackgroundIDBFactoryRequestParent { + protected: + const CommonFactoryRequestParams mCommonParams; + + FactoryRequestOp(SafeRefPtr<Factory> aFactory, + const Maybe<ContentParentId>& aContentParentId, + const CommonFactoryRequestParams& aCommonParams, + bool aDeleting) + : FactoryOp(std::move(aFactory), aContentParentId, + aCommonParams.metadata().persistenceType(), + aCommonParams.principalInfo(), + Some(aCommonParams.metadata().name()), aDeleting), + mCommonParams(aCommonParams) {} + + nsresult DoDirectoryWork() override; + + // IPDL methods. + void ActorDestroy(ActorDestroyReason aWhy) override; }; -class OpenDatabaseOp final : public FactoryOp { +class OpenDatabaseOp final : public FactoryRequestOp { friend class Database; friend class VersionChangeTransaction; @@ -3249,7 +3339,7 @@ class OpenDatabaseOp::VersionChangeOp final explicit VersionChangeOp(OpenDatabaseOp* aOpenDatabaseOp) : TransactionDatabaseOperationBase( aOpenDatabaseOp->mVersionChangeTransaction.clonePtr(), - aOpenDatabaseOp->LoggingSerialNumber()), + /* aRequestId */ 0, aOpenDatabaseOp->LoggingSerialNumber()), mOpenDatabaseOp(aOpenDatabaseOp), mRequestedVersion(aOpenDatabaseOp->mRequestedVersion), mPreviousVersion( @@ -3269,7 +3359,7 @@ class OpenDatabaseOp::VersionChangeOp final void Cleanup() override; }; -class DeleteDatabaseOp final : public FactoryOp { +class DeleteDatabaseOp final : public FactoryRequestOp { class VersionChangeOp; nsString mDatabaseDirectoryPath; @@ -3280,8 +3370,8 @@ class DeleteDatabaseOp final : public FactoryOp { DeleteDatabaseOp(SafeRefPtr<Factory> aFactory, const Maybe<ContentParentId>& aContentParentId, const CommonFactoryRequestParams& aParams) - : FactoryOp(std::move(aFactory), aContentParentId, aParams, - /* aDeleting */ true), + : FactoryRequestOp(std::move(aFactory), aContentParentId, aParams, + /* aDeleting */ true), mPreviousVersion(0) {} private: @@ -3327,6 +3417,43 @@ class DeleteDatabaseOp::VersionChangeOp final : public DatabaseOperationBase { NS_DECL_NSIRUNNABLE }; +class GetDatabasesOp final : public FactoryOp { + nsTHashMap<nsStringHashKey, DatabaseMetadata> mDatabaseMetadataTable; + 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 DoDirectoryWork() override; + + 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; @@ -3334,7 +3461,8 @@ class VersionChangeTransactionOp : public TransactionDatabaseOperationBase { protected: explicit VersionChangeTransactionOp( SafeRefPtr<VersionChangeTransaction> aTransaction) - : TransactionDatabaseOperationBase(std::move(aTransaction)) {} + : TransactionDatabaseOperationBase(std::move(aTransaction), + /* aRequestId */ 0) {} ~VersionChangeTransactionOp() override = default; @@ -3519,8 +3647,9 @@ class NormalTransactionOp : public TransactionDatabaseOperationBase, void Cleanup() override; protected: - explicit NormalTransactionOp(SafeRefPtr<TransactionBase> aTransaction) - : TransactionDatabaseOperationBase(std::move(aTransaction)) + NormalTransactionOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId) + : TransactionDatabaseOperationBase(std::move(aTransaction), aRequestId) #ifdef DEBUG , mResponseSent(false) @@ -3628,7 +3757,7 @@ class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp { }; class SCInputStream; - const ObjectStoreAddPutParams mParams; + ObjectStoreAddPutParams mParams; Maybe<UniqueIndexTable> mUniqueIndexTable; // This must be non-const so that we can update the mNextAutoIncrementId field @@ -3647,6 +3776,7 @@ class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp { private: // Only created by TransactionBase. ObjectStoreAddOrPutRequestOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, RequestParams&& aParams); ~ObjectStoreAddOrPutRequestOp() override = default; @@ -3883,6 +4013,7 @@ class ObjectStoreGetRequestOp final : public NormalTransactionOp { private: // Only created by TransactionBase. ObjectStoreGetRequestOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, const RequestParams& aParams, bool aGetAll); ~ObjectStoreGetRequestOp() override = default; @@ -3912,6 +4043,7 @@ class ObjectStoreGetKeyRequestOp final : public NormalTransactionOp { private: // Only created by TransactionBase. ObjectStoreGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, const RequestParams& aParams, bool aGetAll); ~ObjectStoreGetKeyRequestOp() override = default; @@ -3930,6 +4062,7 @@ class ObjectStoreDeleteRequestOp final : public NormalTransactionOp { private: ObjectStoreDeleteRequestOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, const ObjectStoreDeleteParams& aParams); ~ObjectStoreDeleteRequestOp() override = default; @@ -3951,6 +4084,7 @@ class ObjectStoreClearRequestOp final : public NormalTransactionOp { private: ObjectStoreClearRequestOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, const ObjectStoreClearParams& aParams); ~ObjectStoreClearRequestOp() override = default; @@ -3971,8 +4105,10 @@ class ObjectStoreCountRequestOp final : public NormalTransactionOp { private: ObjectStoreCountRequestOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, const ObjectStoreCountParams& aParams) - : NormalTransactionOp(std::move(aTransaction)), mParams(aParams) {} + : NormalTransactionOp(std::move(aTransaction), aRequestId), + mParams(aParams) {} ~ObjectStoreCountRequestOp() override = default; @@ -3990,8 +4126,8 @@ class IndexRequestOpBase : public NormalTransactionOp { protected: IndexRequestOpBase(SafeRefPtr<TransactionBase> aTransaction, - const RequestParams& aParams) - : NormalTransactionOp(std::move(aTransaction)), + const int64_t aRequestId, const RequestParams& aParams) + : NormalTransactionOp(std::move(aTransaction), aRequestId), mMetadata(IndexMetadataForParams(Transaction(), aParams)) {} ~IndexRequestOpBase() override = default; @@ -4014,7 +4150,8 @@ class IndexGetRequestOp final : public IndexRequestOpBase { private: // Only created by TransactionBase. IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction, - const RequestParams& aParams, bool aGetAll); + const int64_t aRequestId, const RequestParams& aParams, + bool aGetAll); ~IndexGetRequestOp() override = default; @@ -4034,7 +4171,8 @@ class IndexGetKeyRequestOp final : public IndexRequestOpBase { private: // Only created by TransactionBase. IndexGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction, - const RequestParams& aParams, bool aGetAll); + const int64_t aRequestId, const RequestParams& aParams, + bool aGetAll); ~IndexGetKeyRequestOp() override = default; @@ -4052,8 +4190,8 @@ class IndexCountRequestOp final : public IndexRequestOpBase { private: // Only created by TransactionBase. IndexCountRequestOp(SafeRefPtr<TransactionBase> aTransaction, - const RequestParams& aParams) - : IndexRequestOpBase(std::move(aTransaction), aParams), + const int64_t aRequestId, const RequestParams& aParams) + : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams), mParams(aParams.get_IndexCountParams()) {} ~IndexCountRequestOp() override = default; @@ -4153,7 +4291,8 @@ class CursorBase : public PBackgroundIDBCursorParent { ~CursorBase() override { MOZ_ASSERT(!mObjectStoreMetadata); } private: - virtual bool Start(const OpenCursorParams& aParams) = 0; + virtual bool Start(const int64_t aRequestId, + const OpenCursorParams& aParams) = 0; }; class IndexCursorBase : public CursorBase { @@ -4311,7 +4450,7 @@ class Cursor final LazyInitializedOnce<const typename Base::ContinueQueries> mContinueQueries; // Only called by TransactionBase. - bool Start(const OpenCursorParams& aParams) final; + bool Start(const int64_t aRequestId, const OpenCursorParams& aParams) final; void SendResponseInternal(CursorResponse& aResponse, const FilesArrayT<CursorType>& aFiles); @@ -4325,8 +4464,8 @@ class Cursor final mozilla::ipc::IPCResult RecvDeleteMe() override; mozilla::ipc::IPCResult RecvContinue( - const CursorRequestParams& aParams, const Key& aCurrentKey, - const Key& aCurrentObjectStoreKey) override; + const int64_t& aRequestId, const CursorRequestParams& aParams, + const Key& aCurrentKey, const Key& aCurrentObjectStoreKey) override; public: Cursor(SafeRefPtr<TransactionBase> aTransaction, @@ -4373,8 +4512,9 @@ class Cursor<CursorType>::CursorOpBase #endif protected: - explicit CursorOpBase(Cursor* aCursor) - : TransactionDatabaseOperationBase(aCursor->mTransaction.clonePtr()), + explicit CursorOpBase(Cursor* aCursor, const int64_t aRequestId) + : TransactionDatabaseOperationBase(aCursor->mTransaction.clonePtr(), + /* aRequestId */ aRequestId), mCursor(aCursor) #ifdef DEBUG , @@ -4552,9 +4692,10 @@ class Cursor<CursorType>::OpenOp final : public CursorOpBase { using CursorOpBase::mResponse; // Only created by Cursor. - OpenOp(Cursor* const aCursor, + OpenOp(Cursor* const aCursor, const int64_t aRequestId, const Maybe<SerializedKeyRange>& aOptionalKeyRange) - : CursorOpBase(aCursor), mOptionalKeyRange(aOptionalKeyRange) {} + : CursorOpBase(aCursor, aRequestId), + mOptionalKeyRange(aOptionalKeyRange) {} // Reference counted. ~OpenOp() override = default; @@ -4572,9 +4713,9 @@ class Cursor<CursorType>::ContinueOp final const CursorRequestParams mParams; // Only created by Cursor. - ContinueOp(Cursor* const aCursor, CursorRequestParams aParams, - CursorPosition<CursorType> aPosition) - : CursorOpBase(aCursor), + ContinueOp(Cursor* const aCursor, int64_t aRequestId, + CursorRequestParams aParams, CursorPosition<CursorType> aPosition) + : CursorOpBase(aCursor, aRequestId), mParams(std::move(aParams)), mCurrentPosition{std::move(aPosition)} { MOZ_ASSERT(mParams.type() != CursorRequestParams::T__None); @@ -4691,6 +4832,8 @@ class DatabaseLoggingInfo final { }; class QuotaClient final : public mozilla::dom::quota::Client { + friend class GetDatabasesOp; + static QuotaClient* sInstance; nsCOMPtr<nsIEventTarget> mBackgroundThread; @@ -4824,8 +4967,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, @@ -4866,6 +5010,9 @@ class DeleteFilesRunnable final : public Runnable { RefPtr<DirectoryLock> mDirectoryLock; nsTArray<int64_t> mFileIds; State mState; + DEBUGONLY(bool mDEBUGCountsAsPending = false); + + static uint64_t sPendingRunnables; public: DeleteFilesRunnable(SafeRefPtr<DatabaseFileManager> aFileManager, @@ -4873,8 +5020,14 @@ class DeleteFilesRunnable final : public Runnable { void RunImmediately(); + static bool IsDeletionPending() { return sPendingRunnables > 0; } + private: +#ifdef DEBUG + ~DeleteFilesRunnable(); +#else ~DeleteFilesRunnable() = default; +#endif void Open(); @@ -4953,6 +5106,7 @@ class Maintenance final : public Runnable { PRTime mStartTime; RefPtr<UniversalDirectoryLock> mPendingDirectoryLock; RefPtr<UniversalDirectoryLock> mDirectoryLock; + nsTArray<nsCOMPtr<nsIRunnable>> mCompleteCallbacks; nsTArray<DirectoryInfo> mDirectoryInfos; nsTHashMap<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances; nsresult mResultCode; @@ -4994,6 +5148,8 @@ class Maintenance final : public Runnable { void UnregisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance); + bool HasDatabaseMaintenances() const { return mDatabaseMaintenances.Count(); } + RefPtr<DatabaseMaintenance> GetDatabaseMaintenance( const nsAString& aDatabasePath) const { AssertIsOnBackgroundThread(); @@ -5001,6 +5157,13 @@ class Maintenance final : public Runnable { return mDatabaseMaintenances.Get(aDatabasePath); } + void WaitForCompletion(nsIRunnable* aCallback) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mDatabaseMaintenances.Count()); + + mCompleteCallbacks.AppendElement(aCallback); + } + void Stringify(nsACString& aResult) const; private: @@ -6410,9 +6573,9 @@ class DeserializeIndexValueHelper final : public Runnable { value.setUndefined(); ErrorResult rv; - IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mMultiEntry, - mLocale, jsapi.cx(), value, - &mUpdateInfoArray, &rv); + IDBObjectStore::AppendIndexUpdateInfo( + mIndexID, mKeyPath, mMultiEntry, &mUpdateInfoArray, + /* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &rv); return rv.Failed() ? rv.StealNSResult() : NS_OK; } #endif @@ -6450,9 +6613,9 @@ class DeserializeIndexValueHelper final : public Runnable { [this](const nsresult rv) { OperationCompleted(rv); }); ErrorResult errorResult; - IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mMultiEntry, - mLocale, cx, value, &mUpdateInfoArray, - &errorResult); + IDBObjectStore::AppendIndexUpdateInfo( + mIndexID, mKeyPath, mMultiEntry, mLocale, cx, value, &mUpdateInfoArray, + /* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &errorResult); QM_TRY(OkIf(!errorResult.Failed()), NS_OK, ([this, &errorResult](const NotOk) { OperationCompleted(errorResult.StealNSResult()); @@ -9111,20 +9274,9 @@ 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<FactoryOp> { + auto actor = [&]() -> RefPtr<FactoryRequestOp> { if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) { return MakeRefPtr<OpenDatabaseOp>(SafeRefPtrFromThis(), contentParentId, *commonParams); @@ -9151,7 +9303,7 @@ mozilla::ipc::IPCResult Factory::RecvPBackgroundIDBFactoryRequestConstructor( MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); - auto* op = static_cast<FactoryOp*>(aActor); + auto* op = static_cast<FactoryRequestOp*>(aActor); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(op)); return IPC_OK(); @@ -9163,10 +9315,69 @@ bool Factory::DeallocPBackgroundIDBFactoryRequestParent( MOZ_ASSERT(aActor); // Transfer ownership back from IPDL. - RefPtr<FactoryOp> op = dont_AddRef(static_cast<FactoryOp*>(aActor)); + RefPtr<FactoryRequestOp> op = + dont_AddRef(static_cast<FactoryRequestOp*>(aActor)); 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 ******************************************************************************/ @@ -9608,7 +9819,8 @@ bool Database::DeallocPBackgroundIDBDatabaseFileParent( already_AddRefed<PBackgroundIDBTransactionParent> Database::AllocPBackgroundIDBTransactionParent( - const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode) { + const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode, + const Durability& aDurability) { AssertIsOnBackgroundThread(); // Once a database is closed it must not try to open new transactions. @@ -9628,6 +9840,12 @@ Database::AllocPBackgroundIDBTransactionParent( return nullptr; } + if (NS_AUUF_OR_WARN_IF(aDurability != IDBTransaction::Durability::Default && + aDurability != IDBTransaction::Durability::Strict && + aDurability != IDBTransaction::Durability::Relaxed)) { + return nullptr; + } + const ObjectStoreTable& objectStores = mMetadata->mObjectStores; const uint32_t nameCount = aObjectStoreNames.Length(); @@ -9670,13 +9888,15 @@ Database::AllocPBackgroundIDBTransactionParent( nullptr); return MakeSafeRefPtr<NormalTransaction>(SafeRefPtrFromThis(), aMode, + aDurability, std::move(objectStoreMetadatas)) .forget(); } mozilla::ipc::IPCResult Database::RecvPBackgroundIDBTransactionConstructor( PBackgroundIDBTransactionParent* aActor, - nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode) { + nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode, + const Durability& aDurability) { // TODO: See bug 1883045 AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(!aObjectStoreNames.IsEmpty()); @@ -9684,6 +9904,9 @@ mozilla::ipc::IPCResult Database::RecvPBackgroundIDBTransactionConstructor( aMode == IDBTransaction::Mode::ReadWrite || aMode == IDBTransaction::Mode::ReadWriteFlush || aMode == IDBTransaction::Mode::Cleanup); + MOZ_ASSERT(aDurability == IDBTransaction::Durability::Default || + aDurability == IDBTransaction::Durability::Strict || + aDurability == IDBTransaction::Durability::Relaxed); MOZ_ASSERT(!mClosed); if (IsInvalidated()) { @@ -9815,7 +10038,8 @@ void Database::StartTransactionOp::Cleanup() { * TransactionBase ******************************************************************************/ -TransactionBase::TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode) +TransactionBase::TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode, + Durability aDurability) : mDatabase(std::move(aDatabase)), mDatabaseId(mDatabase->Id()), mLoggingSerialNumber( @@ -9823,6 +10047,7 @@ TransactionBase::TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode) mActiveRequestCount(0), mInvalidatedOnAnyThread(false), mMode(aMode), + mDurability(aDurability), mResultCode(NS_OK) { AssertIsOnBackgroundThread(); MOZ_ASSERT(mDatabase); @@ -9899,14 +10124,13 @@ void TransactionBase::CommitOrAbort() { return; } - // In case of a failed request that was started after committing was - // initiated, abort (cf. - // https://w3c.github.io/IndexedDB/#async-execute-request step 5.3 vs. 5.4). - // Note this can only happen here when we are committing explicitly, otherwise - // the decision is made by the child. + // In case of a failed request and explicitly committed transaction, abort + // (cf. https://w3c.github.io/IndexedDB/#async-execute-request step 5.3 + // vs. 5.4). It's worth emphasizing this can only happen here when we are + // committing explicitly, otherwise the decision is made by the child. if (NS_SUCCEEDED(mResultCode) && mLastFailedRequest && *mLastRequestBeforeCommit && - *mLastFailedRequest >= **mLastRequestBeforeCommit) { + *mLastFailedRequest == **mLastRequestBeforeCommit) { mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR; } @@ -10360,7 +10584,7 @@ void TransactionBase::Invalidate() { } PBackgroundIDBRequestParent* TransactionBase::AllocRequest( - RequestParams&& aParams, bool aTrustParams) { + const int64_t aRequestId, RequestParams&& aParams, bool aTrustParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aParams.type() != RequestParams::T__None); @@ -10382,67 +10606,77 @@ PBackgroundIDBRequestParent* TransactionBase::AllocRequest( switch (aParams.type()) { case RequestParams::TObjectStoreAddParams: case RequestParams::TObjectStorePutParams: - actor = new ObjectStoreAddOrPutRequestOp(SafeRefPtrFromThis(), + actor = new ObjectStoreAddOrPutRequestOp(SafeRefPtrFromThis(), aRequestId, std::move(aParams)); break; case RequestParams::TObjectStoreGetParams: - actor = new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aParams, - /* aGetAll */ false); + actor = + new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, + /* aGetAll */ false); break; case RequestParams::TObjectStoreGetAllParams: - actor = new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aParams, - /* aGetAll */ true); + actor = + new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, + /* aGetAll */ true); break; case RequestParams::TObjectStoreGetKeyParams: - actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aParams, + actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, + aParams, /* aGetAll */ false); break; case RequestParams::TObjectStoreGetAllKeysParams: - actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aParams, + actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, + aParams, /* aGetAll */ true); break; case RequestParams::TObjectStoreDeleteParams: - actor = new ObjectStoreDeleteRequestOp( - SafeRefPtrFromThis(), aParams.get_ObjectStoreDeleteParams()); + actor = + new ObjectStoreDeleteRequestOp(SafeRefPtrFromThis(), aRequestId, + aParams.get_ObjectStoreDeleteParams()); break; case RequestParams::TObjectStoreClearParams: - actor = new ObjectStoreClearRequestOp( - SafeRefPtrFromThis(), aParams.get_ObjectStoreClearParams()); + actor = + new ObjectStoreClearRequestOp(SafeRefPtrFromThis(), aRequestId, + aParams.get_ObjectStoreClearParams()); break; case RequestParams::TObjectStoreCountParams: - actor = new ObjectStoreCountRequestOp( - SafeRefPtrFromThis(), aParams.get_ObjectStoreCountParams()); + actor = + new ObjectStoreCountRequestOp(SafeRefPtrFromThis(), aRequestId, + aParams.get_ObjectStoreCountParams()); break; case RequestParams::TIndexGetParams: - actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aParams, + actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, /* aGetAll */ false); break; case RequestParams::TIndexGetKeyParams: - actor = new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aParams, - /* aGetAll */ false); + actor = + new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, + /* aGetAll */ false); break; case RequestParams::TIndexGetAllParams: - actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aParams, + actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, /* aGetAll */ true); break; case RequestParams::TIndexGetAllKeysParams: - actor = new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aParams, - /* aGetAll */ true); + actor = + new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, + /* aGetAll */ true); break; case RequestParams::TIndexCountParams: - actor = new IndexCountRequestOp(SafeRefPtrFromThis(), aParams); + actor = + new IndexCountRequestOp(SafeRefPtrFromThis(), aRequestId, aParams); break; default: @@ -10552,6 +10786,7 @@ already_AddRefed<PBackgroundIDBCursorParent> TransactionBase::AllocCursor( } bool TransactionBase::StartCursor(PBackgroundIDBCursorParent* const aActor, + const int64_t aRequestId, const OpenCursorParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); @@ -10559,7 +10794,7 @@ bool TransactionBase::StartCursor(PBackgroundIDBCursorParent* const aActor, auto* const op = static_cast<CursorBase*>(aActor); - if (NS_WARN_IF(!op->Start(aParams))) { + if (NS_WARN_IF(!op->Start(aRequestId, aParams))) { return false; } @@ -10572,8 +10807,9 @@ bool TransactionBase::StartCursor(PBackgroundIDBCursorParent* const aActor, NormalTransaction::NormalTransaction( SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode, + TransactionBase::Durability aDurability, nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores) - : TransactionBase(std::move(aDatabase), aMode), + : TransactionBase(std::move(aDatabase), aMode, aDurability), mObjectStores{std::move(aObjectStores)} { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mObjectStores.IsEmpty()); @@ -10638,16 +10874,18 @@ mozilla::ipc::IPCResult NormalTransaction::RecvAbort( PBackgroundIDBRequestParent* NormalTransaction::AllocPBackgroundIDBRequestParent( - const RequestParams& aParams) { + const int64_t& aRequestId, const RequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aParams.type() != RequestParams::T__None); - return AllocRequest(std::move(const_cast<RequestParams&>(aParams)), + return AllocRequest(aRequestId, + std::move(const_cast<RequestParams&>(aParams)), IsSameProcessActor()); } mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBRequestConstructor( - PBackgroundIDBRequestParent* const aActor, const RequestParams& aParams) { + PBackgroundIDBRequestParent* const aActor, const int64_t& aRequestId, + const RequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != RequestParams::T__None); @@ -10668,19 +10906,20 @@ bool NormalTransaction::DeallocPBackgroundIDBRequestParent( already_AddRefed<PBackgroundIDBCursorParent> NormalTransaction::AllocPBackgroundIDBCursorParent( - const OpenCursorParams& aParams) { + const int64_t& aRequestId, const OpenCursorParams& aParams) { AssertIsOnBackgroundThread(); return AllocCursor(aParams, IsSameProcessActor()); } mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBCursorConstructor( - PBackgroundIDBCursorParent* const aActor, const OpenCursorParams& aParams) { + PBackgroundIDBCursorParent* const aActor, const int64_t& aRequestId, + const OpenCursorParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); - if (!StartCursor(aActor, aParams)) { + if (!StartCursor(aActor, aRequestId, aParams)) { return IPC_FAIL(this, "StartCursor failed!"); } return IPC_OK(); @@ -10693,7 +10932,9 @@ mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBCursorConstructor( VersionChangeTransaction::VersionChangeTransaction( OpenDatabaseOp* aOpenDatabaseOp) : TransactionBase(aOpenDatabaseOp->mDatabase.clonePtr(), - IDBTransaction::Mode::VersionChange), + IDBTransaction::Mode::VersionChange, + // VersionChange must not change durability. + IDBTransaction::Durability::Default), // Not used. mOpenDatabaseOp(aOpenDatabaseOp) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aOpenDatabaseOp); @@ -10755,7 +10996,7 @@ void VersionChangeTransaction::UpdateMetadata(nsresult aResult) { AssertIsOnBackgroundThread(); MOZ_ASSERT(mOpenDatabaseOp); MOZ_ASSERT(!!mActorWasAlive == !!mOpenDatabaseOp->mDatabase); - MOZ_ASSERT_IF(mActorWasAlive, !mOpenDatabaseOp->mDatabaseId.IsEmpty()); + MOZ_ASSERT_IF(mActorWasAlive, !mOpenDatabaseOp->mDatabaseId.ref().IsEmpty()); if (IsActorDestroyed() || !mActorWasAlive) { return; @@ -11224,17 +11465,19 @@ mozilla::ipc::IPCResult VersionChangeTransaction::RecvRenameIndex( PBackgroundIDBRequestParent* VersionChangeTransaction::AllocPBackgroundIDBRequestParent( - const RequestParams& aParams) { + const int64_t& aRequestId, const RequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aParams.type() != RequestParams::T__None); - return AllocRequest(std::move(const_cast<RequestParams&>(aParams)), + return AllocRequest(aRequestId, + std::move(const_cast<RequestParams&>(aParams)), IsSameProcessActor()); } mozilla::ipc::IPCResult VersionChangeTransaction::RecvPBackgroundIDBRequestConstructor( - PBackgroundIDBRequestParent* aActor, const RequestParams& aParams) { + PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId, + const RequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != RequestParams::T__None); @@ -11255,7 +11498,7 @@ bool VersionChangeTransaction::DeallocPBackgroundIDBRequestParent( already_AddRefed<PBackgroundIDBCursorParent> VersionChangeTransaction::AllocPBackgroundIDBCursorParent( - const OpenCursorParams& aParams) { + const int64_t& aRequestId, const OpenCursorParams& aParams) { AssertIsOnBackgroundThread(); return AllocCursor(aParams, IsSameProcessActor()); @@ -11263,12 +11506,13 @@ VersionChangeTransaction::AllocPBackgroundIDBCursorParent( mozilla::ipc::IPCResult VersionChangeTransaction::RecvPBackgroundIDBCursorConstructor( - PBackgroundIDBCursorParent* aActor, const OpenCursorParams& aParams) { + PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId, + const OpenCursorParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); - if (!StartCursor(aActor, aParams)) { + if (!StartCursor(aActor, aRequestId, aParams)) { return IPC_FAIL(this, "StartCursor failed!"); } return IPC_OK(); @@ -11417,7 +11661,8 @@ bool Cursor<CursorType>::VerifyRequestParams( } template <IDBCursorType CursorType> -bool Cursor<CursorType>::Start(const OpenCursorParams& aParams) { +bool Cursor<CursorType>::Start(const int64_t aRequestId, + const OpenCursorParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aParams.type() == ToOpenCursorParamsType(CursorType)); MOZ_ASSERT(this->mObjectStoreMetadata); @@ -11429,7 +11674,7 @@ bool Cursor<CursorType>::Start(const OpenCursorParams& aParams) { const Maybe<SerializedKeyRange>& optionalKeyRange = GetCommonOpenCursorParams(aParams).optionalKeyRange(); - const RefPtr<OpenOp> openOp = new OpenOp(this, optionalKeyRange); + const RefPtr<OpenOp> openOp = new OpenOp(this, aRequestId, optionalKeyRange); if (NS_WARN_IF(!openOp->Init(*mTransaction))) { openOp->Cleanup(); @@ -11553,8 +11798,8 @@ mozilla::ipc::IPCResult Cursor<CursorType>::RecvDeleteMe() { template <IDBCursorType CursorType> mozilla::ipc::IPCResult Cursor<CursorType>::RecvContinue( - const CursorRequestParams& aParams, const Key& aCurrentKey, - const Key& aCurrentObjectStoreKey) { + const int64_t& aRequestId, const CursorRequestParams& aParams, + const Key& aCurrentKey, const Key& aCurrentObjectStoreKey) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None); MOZ_ASSERT(this->mObjectStoreMetadata); @@ -11604,7 +11849,7 @@ mozilla::ipc::IPCResult Cursor<CursorType>::RecvContinue( } const RefPtr<ContinueOp> continueOp = - new ContinueOp(this, aParams, std::move(position)); + new ContinueOp(this, aRequestId, aParams, std::move(position)); if (NS_WARN_IF(!continueOp->Init(*mTransaction))) { continueOp->Cleanup(); return IPC_FAIL(this, "ContinueOp initialization failed!"); @@ -11626,15 +11871,18 @@ DatabaseFileManager::DatabaseFileManager( PersistenceType aPersistenceType, const quota::OriginMetadata& aOriginMetadata, const nsAString& aDatabaseName, const nsCString& aDatabaseID, - bool aEnforcingQuota, bool aIsInPrivateBrowsingMode) + const nsAString& aDatabaseFilePath, bool aEnforcingQuota, + bool aIsInPrivateBrowsingMode) : mPersistenceType(aPersistenceType), mOriginMetadata(aOriginMetadata), mDatabaseName(aDatabaseName), mDatabaseID(aDatabaseID), + mDatabaseFilePath(aDatabaseFilePath), mCipherKeyManager( aIsInPrivateBrowsingMode ? new IndexedDBCipherKeyManager("IndexedDBCipherKeyManager") : nullptr), + mDatabaseVersion(0), mEnforcingQuota(aEnforcingQuota), mIsInPrivateBrowsingMode(aIsInPrivateBrowsingMode) {} @@ -12488,6 +12736,17 @@ void QuotaClient::StopIdleMaintenance() { void QuotaClient::InitiateShutdown() { AssertIsOnBackgroundThread(); + MOZ_ASSERT(IsShuttingDownOnBackgroundThread()); + + if (mDeleteTimer) { + // QuotaClient::AsyncDeleteFile will not schedule new timers beyond + // shutdown. And we expect all critical (PBM) deletions to have been + // triggered before this point via ClearPrivateRepository (w/out using + // DeleteFilesRunnable at all). + mDeleteTimer->Cancel(); + mDeleteTimer = nullptr; + mPendingDeleteInfos.Clear(); + } AbortAllOperations(); } @@ -12495,7 +12754,7 @@ void QuotaClient::InitiateShutdown() { bool QuotaClient::IsShutdownCompleted() const { return (!gFactoryOps || gFactoryOps->IsEmpty()) && (!gLiveDatabaseHashtable || !gLiveDatabaseHashtable->Count()) && - !mCurrentMaintenance; + !mCurrentMaintenance && !DeleteFilesRunnable::IsDeletionPending(); } void QuotaClient::ForceKillActors() { @@ -12575,17 +12834,20 @@ void QuotaClient::FinalizeShutdown() { mMaintenanceThreadPool->Shutdown(); mMaintenanceThreadPool = nullptr; } - - if (mDeleteTimer) { - MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel()); - mDeleteTimer = nullptr; - } } void QuotaClient::DeleteTimerCallback(nsITimer* aTimer, void* aClosure) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aTimer); + // Even though we do not schedule new timers after shutdown has started, + // an already existing one might fire afterwards (actually we think it + // shouldn't, but there is no reason to enforce this invariant). We can + // just ignore it, the cleanup work is done in InitiateShutdown. + if (NS_WARN_IF(IsShuttingDownOnBackgroundThread())) { + return; + } + auto* const self = static_cast<QuotaClient*>(aClosure); MOZ_ASSERT(self); MOZ_ASSERT(self->mDeleteTimer); @@ -12728,6 +12990,8 @@ void QuotaClient::ProcessMaintenanceQueue() { * DeleteFilesRunnable ******************************************************************************/ +uint64_t DeleteFilesRunnable::sPendingRunnables = 0; + DeleteFilesRunnable::DeleteFilesRunnable( SafeRefPtr<DatabaseFileManager> aFileManager, nsTArray<int64_t>&& aFileIds) : Runnable("dom::indexeddb::DeleteFilesRunnable"), @@ -12736,6 +13000,12 @@ DeleteFilesRunnable::DeleteFilesRunnable( mFileIds(std::move(aFileIds)), mState(State_Initial) {} +#ifdef DEBUG +DeleteFilesRunnable::~DeleteFilesRunnable() { + MOZ_ASSERT(!mDEBUGCountsAsPending); +} +#endif + void DeleteFilesRunnable::RunImmediately() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mState == State_Initial); @@ -12747,6 +13017,10 @@ void DeleteFilesRunnable::Open() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mState == State_Initial); + MOZ_ASSERT(!mDEBUGCountsAsPending); + sPendingRunnables++; + DEBUGONLY(mDEBUGCountsAsPending = true); + QuotaManager* const quotaManager = QuotaManager::Get(); if (NS_WARN_IF(!quotaManager)) { Finish(); @@ -12800,6 +13074,9 @@ void DeleteFilesRunnable::UnblockOpen() { MOZ_ASSERT(mState == State_UnblockingOpen); mDirectoryLock = nullptr; + MOZ_ASSERT(mDEBUGCountsAsPending); + sPendingRunnables--; + DEBUGONLY(mDEBUGCountsAsPending = false); mState = State_Completed; } @@ -12892,6 +13169,11 @@ void Maintenance::UnregisterDatabaseMaintenance( return; } + for (const auto& completeCallback : mCompleteCallbacks) { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(completeCallback)); + } + mCompleteCallbacks.Clear(); + mState = State::Finishing; Finish(); } @@ -13289,6 +13571,10 @@ nsresult Maintenance::BeginDatabaseMaintenance() { for (uint32_t index = gFactoryOps->Length(); index > 0; index--) { CheckedUnsafePtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1]; + if (existingOp->DatabaseNameRef().isNothing()) { + return false; + } + if (!existingOp->DatabaseFilePathIsKnown()) { continue; } @@ -14448,14 +14734,17 @@ void DatabaseOperationBase::AutoSetProgressHandler::Unregister() { FactoryOp::FactoryOp(SafeRefPtr<Factory> aFactory, const Maybe<ContentParentId>& aContentParentId, - const CommonFactoryRequestParams& aCommonParams, - bool aDeleting) + const PersistenceType aPersistenceType, + const PrincipalInfo& aPrincipalInfo, + const Maybe<nsString>& aDatabaseName, bool aDeleting) : DatabaseOperationBase(aFactory->GetLoggingInfo()->Id(), aFactory->GetLoggingInfo()->NextRequestSN()), mFactory(std::move(aFactory)), mContentParentId(aContentParentId), - mCommonParams(aCommonParams), + mPrincipalInfo(aPrincipalInfo), + mDatabaseName(aDatabaseName), mDirectoryLockId(-1), + mPersistenceType(aPersistenceType), mState(State::Initial), mWaitingForPermissionRetry(false), mEnforcingQuota(true), @@ -14506,7 +14795,7 @@ void FactoryOp::NoteDatabaseClosed(Database* const aDatabase) { } DatabaseActorInfo* info; - MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info)); + MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)); MOZ_ASSERT(info->mWaitingFactoryOp == this); if (AreActorsAlive()) { @@ -14547,6 +14836,14 @@ void FactoryOp::StringifyState(nsACString& aResult) const { aResult.AppendLiteral("DirectoryOpenPending"); return; + case State::DirectoryWorkOpen: + aResult.AppendLiteral("DirectoryWorkOpen"); + return; + + case State::DirectoryWorkDone: + aResult.AppendLiteral("DirectoryWorkDone"); + return; + case State::DatabaseOpenPending: aResult.AppendLiteral("DatabaseOpenPending"); return; @@ -14588,8 +14885,7 @@ void FactoryOp::Stringify(nsACString& aResult) const { AssertIsOnOwningThread(); aResult.AppendLiteral("PersistenceType:"); - aResult.Append( - PersistenceTypeToString(mCommonParams.metadata().persistenceType())); + aResult.Append(PersistenceTypeToString(mPersistenceType)); aResult.Append(kQuotaGenericDelimiter); aResult.AppendLiteral("Origin:"); @@ -14617,32 +14913,25 @@ nsresult FactoryOp::Open() { QuotaManager* const quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); - const DatabaseMetadata& metadata = mCommonParams.metadata(); - - const PersistenceType persistenceType = metadata.persistenceType(); - - const PrincipalInfo& principalInfo = mCommonParams.principalInfo(); - - QM_TRY_UNWRAP(auto principalMetadata, - quotaManager->GetInfoFromValidatedPrincipalInfo(principalInfo)); + QM_TRY_UNWRAP( + auto principalMetadata, + quotaManager->GetInfoFromValidatedPrincipalInfo(mPrincipalInfo)); - mOriginMetadata = {std::move(principalMetadata), persistenceType}; + mOriginMetadata = {std::move(principalMetadata), mPersistenceType}; - if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { - MOZ_ASSERT(mCommonParams.metadata().persistenceType() == - PERSISTENCE_TYPE_PERSISTENT); + if (mPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { + MOZ_ASSERT(mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); mEnforcingQuota = false; - } else if (principalInfo.type() == PrincipalInfo::TContentPrincipalInfo) { + } else if (mPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo) { const ContentPrincipalInfo& contentPrincipalInfo = - principalInfo.get_ContentPrincipalInfo(); + mPrincipalInfo.get_ContentPrincipalInfo(); MOZ_ASSERT_IF( QuotaManager::IsOriginInternal(contentPrincipalInfo.originNoSuffix()), - mCommonParams.metadata().persistenceType() == - PERSISTENCE_TYPE_PERSISTENT); + mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); - mEnforcingQuota = persistenceType != PERSISTENCE_TYPE_PERSISTENT; + mEnforcingQuota = mPersistenceType != PERSISTENCE_TYPE_PERSISTENT; if (mOriginMetadata.mIsPrivate) { if (StaticPrefs::dom_indexedDB_privateBrowsing_enabled()) { @@ -14663,31 +14952,39 @@ nsresult FactoryOp::Open() { MOZ_ASSERT(false); } - QuotaManager::GetStorageId(persistenceType, mOriginMetadata.mOrigin, - Client::IDB, mDatabaseId); + if (mDatabaseName.isSome()) { + nsCString databaseId; - mDatabaseId.Append('*'); - mDatabaseId.Append(NS_ConvertUTF16toUTF8(metadata.name())); + QuotaManager::GetStorageId(mPersistenceType, mOriginMetadata.mOrigin, + Client::IDB, databaseId); - // Need to get database file path before opening the directory. - // XXX: For what reason? - QM_TRY_UNWRAP( - mDatabaseFilePath, - ([this, metadata, quotaManager]() -> mozilla::Result<nsString, nsresult> { - QM_TRY_INSPECT(const auto& dbFile, - quotaManager->GetOriginDirectory(mOriginMetadata)); + databaseId.Append('*'); + databaseId.Append(NS_ConvertUTF16toUTF8(mDatabaseName.ref())); - QM_TRY(MOZ_TO_RESULT(dbFile->Append( - NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); + mDatabaseId = Some(std::move(databaseId)); - QM_TRY(MOZ_TO_RESULT( - dbFile->Append(GetDatabaseFilenameBase(metadata.name(), - mOriginMetadata.mIsPrivate) + - kSQLiteSuffix))); + // Need to get database file path before opening the directory. + // XXX: For what reason? + QM_TRY_UNWRAP( + auto databaseFilePath, + ([this, quotaManager]() -> mozilla::Result<nsString, nsresult> { + QM_TRY_INSPECT(const auto& dbFile, + quotaManager->GetOriginDirectory(mOriginMetadata)); - QM_TRY_RETURN( - MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); - }())); + QM_TRY(MOZ_TO_RESULT(dbFile->Append( + NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); + + QM_TRY(MOZ_TO_RESULT(dbFile->Append( + GetDatabaseFilenameBase(mDatabaseName.ref(), + mOriginMetadata.mIsPrivate) + + kSQLiteSuffix))); + + QM_TRY_RETURN( + MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); + }())); + + mDatabaseFilePath = Some(std::move(databaseFilePath)); + } // Open directory mState = State::DirectoryOpenPending; @@ -14711,47 +15008,77 @@ nsresult FactoryOp::DirectoryOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::DirectoryOpenPending); MOZ_ASSERT(mDirectoryLock); - MOZ_ASSERT(!mDatabaseFilePath.IsEmpty()); + + if (mDatabaseName.isNothing()) { + QuotaManager* const quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + // Must set this before dispatching otherwise we will race with the IO + // thread. + mState = State::DirectoryWorkOpen; + + QM_TRY(MOZ_TO_RESULT( + quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)), + NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA); + + return NS_OK; + } + + mState = State::DirectoryWorkDone; + MOZ_ALWAYS_SUCCEEDS(Run()); + + return NS_OK; +} + +nsresult FactoryOp::DirectoryWorkDone() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::DirectoryWorkDone); + MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(gFactoryOps); // See if this FactoryOp needs to wait. - const bool delayed = - std::any_of( - gFactoryOps->rbegin(), gFactoryOps->rend(), - [foundThis = false, &self = *this](const auto& existingOp) mutable { - if (existingOp == &self) { - foundThis = true; - return false; - } + const bool blocked = [&self = *this] { + bool foundThis = false; + bool blocked = false; - if (foundThis && self.MustWaitFor(*existingOp)) { - // Only one op can be delayed. - MOZ_ASSERT(!existingOp->mDelayedOp); - existingOp->mDelayedOp = &self; - return true; - } + for (const auto& existingOp : Reversed(*gFactoryOps)) { + if (existingOp == &self) { + foundThis = true; + continue; + } - return false; - }) || - [&self = *this] { - QuotaClient* quotaClient = QuotaClient::GetInstance(); - MOZ_ASSERT(quotaClient); - - if (RefPtr<Maintenance> currentMaintenance = - quotaClient->GetCurrentMaintenance()) { - if (RefPtr<DatabaseMaintenance> databaseMaintenance = - currentMaintenance->GetDatabaseMaintenance( - self.mDatabaseFilePath)) { - databaseMaintenance->WaitForCompletion(&self); - return true; - } + if (foundThis && self.MustWaitFor(*existingOp)) { + existingOp->AddBlockingOp(self); + self.AddBlockedOnOp(*existingOp); + blocked = true; + } + } + + return blocked; + }() || [&self = *this] { + QuotaClient* quotaClient = QuotaClient::GetInstance(); + MOZ_ASSERT(quotaClient); + + if (RefPtr<Maintenance> currentMaintenance = + quotaClient->GetCurrentMaintenance()) { + if (self.mDatabaseName.isSome()) { + if (RefPtr<DatabaseMaintenance> databaseMaintenance = + currentMaintenance->GetDatabaseMaintenance( + self.mDatabaseFilePath.ref())) { + databaseMaintenance->WaitForCompletion(&self); + return true; } + } else if (currentMaintenance->HasDatabaseMaintenances()) { + currentMaintenance->WaitForCompletion(&self); + return true; + } + } - return false; - }(); + return false; + }(); mState = State::DatabaseOpenPending; - if (!delayed) { + if (!blocked) { QM_TRY(MOZ_TO_RESULT(DatabaseOpen())); } @@ -14785,22 +15112,23 @@ void FactoryOp::WaitForTransactions() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::BeginVersionChange || mState == State::WaitingForOtherDatabasesToClose); - MOZ_ASSERT(!mDatabaseId.IsEmpty()); + MOZ_ASSERT(!mDatabaseId.ref().IsEmpty()); MOZ_ASSERT(!IsActorDestroyed()); mState = State::WaitingForTransactionsToComplete; RefPtr<WaitForTransactionsHelper> helper = - new WaitForTransactionsHelper(mDatabaseId, this); + new WaitForTransactionsHelper(mDatabaseId.ref(), this); helper->WaitForTransactions(); } void FactoryOp::CleanupMetadata() { AssertIsOnOwningThread(); - if (mDelayedOp) { - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget())); + for (const NotNull<RefPtr<FactoryOp>>& blockingOp : mBlocking) { + blockingOp->MaybeUnblock(*this); } + mBlocking.Clear(); MOZ_ASSERT(gFactoryOps); gFactoryOps->RemoveElement(this); @@ -14868,12 +15196,23 @@ nsresult FactoryOp::SendVersionChangeMessages( bool FactoryOp::MustWaitFor(const FactoryOp& aExistingOp) { AssertIsOnOwningThread(); - // Things for the same persistence type, the same origin and the same - // database must wait. - return aExistingOp.mCommonParams.metadata().persistenceType() == - mCommonParams.metadata().persistenceType() && - aExistingOp.mOriginMetadata.mOrigin == mOriginMetadata.mOrigin && - aExistingOp.mDatabaseId == mDatabaseId; + // If the persistence types don't overlap, the op can proceed. + if (aExistingOp.mPersistenceType != mPersistenceType) { + return false; + } + + // If the origins don't overlap, the op can proceed. + if (aExistingOp.mOriginMetadata.mOrigin != mOriginMetadata.mOrigin) { + return false; + } + + // If the database ids don't overlap, the op can proceed. + if (!aExistingOp.mDatabaseId.isNothing() && !mDatabaseId.isNothing() && + aExistingOp.mDatabaseId.ref() != mDatabaseId.ref()) { + return false; + } + + return true; } // Run() assumes that the caller holds a strong reference to the object that @@ -14905,6 +15244,14 @@ FactoryOp::Run() { QM_WARNONLY_TRY(MOZ_TO_RESULT(Open()), handleError); break; + case State::DirectoryWorkOpen: + QM_WARNONLY_TRY(MOZ_TO_RESULT(DoDirectoryWork()), handleError); + break; + + case State::DirectoryWorkDone: + QM_WARNONLY_TRY(MOZ_TO_RESULT(DirectoryWorkDone()), handleError); + break; + case State::DatabaseOpenPending: QM_WARNONLY_TRY(MOZ_TO_RESULT(DatabaseOpen()), handleError); break; @@ -14971,7 +15318,11 @@ void FactoryOp::DirectoryLockFailed() { MOZ_ALWAYS_SUCCEEDS(Run()); } -void FactoryOp::ActorDestroy(ActorDestroyReason aWhy) { +nsresult FactoryRequestOp::DoDirectoryWork() { + MOZ_CRASH("Not implemented because this should be unreachable."); +} + +void FactoryRequestOp::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnBackgroundThread(); NoteActorDestroyed(); @@ -14980,8 +15331,8 @@ void FactoryOp::ActorDestroy(ActorDestroyReason aWhy) { OpenDatabaseOp::OpenDatabaseOp(SafeRefPtr<Factory> aFactory, const Maybe<ContentParentId>& aContentParentId, const CommonFactoryRequestParams& aParams) - : FactoryOp(std::move(aFactory), aContentParentId, aParams, - /* aDeleting */ false), + : FactoryRequestOp(std::move(aFactory), aContentParentId, aParams, + /* aDeleting */ false), mMetadata(MakeSafeRefPtr<FullDatabaseMetadata>(aParams.metadata())), mRequestedVersion(aParams.metadata().version()), mVersionChangeOp(nullptr), @@ -14990,7 +15341,7 @@ OpenDatabaseOp::OpenDatabaseOp(SafeRefPtr<Factory> aFactory, void OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); - FactoryOp::ActorDestroy(aWhy); + FactoryRequestOp::ActorDestroy(aWhy); if (mVersionChangeOp) { mVersionChangeOp->NoteActorDestroyed(); @@ -15097,7 +15448,7 @@ nsresult OpenDatabaseOp::DoDatabaseWork() { const auto& databaseFilePath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); - MOZ_ASSERT(databaseFilePath == mDatabaseFilePath); + MOZ_ASSERT(databaseFilePath == mDatabaseFilePath.ref()); } #endif @@ -15114,8 +15465,8 @@ nsresult OpenDatabaseOp::DoDatabaseWork() { if (!fileManager) { fileManager = MakeSafeRefPtr<DatabaseFileManager>( - persistenceType, mOriginMetadata, databaseName, mDatabaseId, - mEnforcingQuota, mInPrivateBrowsing); + persistenceType, mOriginMetadata, databaseName, mDatabaseId.ref(), + mDatabaseFilePath.ref(), mEnforcingQuota, mInPrivateBrowsing); } Maybe<const CipherKey> maybeKey = @@ -15555,7 +15906,7 @@ nsresult OpenDatabaseOp::BeginVersionChange() { MOZ_ASSERT(!mDatabase->IsClosed()); DatabaseActorInfo* info; - MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info)); + MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)); MOZ_ASSERT(info->mLiveDatabases.Contains(mDatabase.unsafeGetRawPtr())); MOZ_ASSERT(!info->mWaitingFactoryOp); @@ -15699,9 +16050,9 @@ void OpenDatabaseOp::SendResults() { MOZ_ASSERT_IF(!HasFailed(), !mVersionChangeTransaction); DebugOnly<DatabaseActorInfo*> info = nullptr; - MOZ_ASSERT_IF( - gLiveDatabaseHashtable && gLiveDatabaseHashtable->Get(mDatabaseId, &info), - !info->mWaitingFactoryOp); + MOZ_ASSERT_IF(mDatabaseId.isSome() && gLiveDatabaseHashtable && + gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info), + !info->mWaitingFactoryOp); if (mVersionChangeTransaction) { MOZ_ASSERT(HasFailed()); @@ -15720,6 +16071,8 @@ void OpenDatabaseOp::SendResults() { // need to update the version in our metadata. mMetadata->mCommonMetadata.version() = mRequestedVersion; + mFileManager->UpdateDatabaseVersion(mRequestedVersion); + nsresult rv = EnsureDatabaseActorIsAlive(); if (NS_SUCCEEDED(rv)) { // We successfully opened a database so use its actor as the success @@ -15767,7 +16120,7 @@ void OpenDatabaseOp::SendResults() { &OpenDatabaseOp::ConnectionClosedCallback); RefPtr<WaitForTransactionsHelper> helper = - new WaitForTransactionsHelper(mDatabaseId, callback); + new WaitForTransactionsHelper(mDatabaseId.ref(), callback); helper->WaitForTransactions(); } else { CleanupMetadata(); @@ -15792,7 +16145,7 @@ void OpenDatabaseOp::EnsureDatabaseActor() { mState == State::DatabaseWorkVersionChange || mState == State::SendingResults); MOZ_ASSERT(!HasFailed()); - MOZ_ASSERT(!mDatabaseFilePath.IsEmpty()); + MOZ_ASSERT(mDatabaseFilePath.isSome()); MOZ_ASSERT(!IsActorDestroyed()); if (mDatabase) { @@ -15800,13 +16153,13 @@ void OpenDatabaseOp::EnsureDatabaseActor() { } MOZ_ASSERT(mMetadata->mDatabaseId.IsEmpty()); - mMetadata->mDatabaseId = mDatabaseId; + mMetadata->mDatabaseId = mDatabaseId.ref(); MOZ_ASSERT(mMetadata->mFilePath.IsEmpty()); - mMetadata->mFilePath = mDatabaseFilePath; + mMetadata->mFilePath = mDatabaseFilePath.ref(); DatabaseActorInfo* info; - if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) { + if (gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)) { AssertMetadataConsistency(*info->mMetadata); mMetadata = info->mMetadata.clonePtr(); } @@ -15833,7 +16186,7 @@ void OpenDatabaseOp::EnsureDatabaseActor() { // XXX Maybe use LookupOrInsertWith above, to avoid a second lookup here? info = gLiveDatabaseHashtable ->InsertOrUpdate( - mDatabaseId, + mDatabaseId.ref(), MakeUnique<DatabaseActorInfo>( mMetadata.clonePtr(), WrapNotNullUnchecked(mDatabase.unsafeGetRawPtr()))) @@ -16128,8 +16481,8 @@ void DeleteDatabaseOp::LoadPreviousVersion(nsIFile& aDatabaseFile) { if (!fileManager) { fileManager = MakeSafeRefPtr<DatabaseFileManager>( - persistenceType, mOriginMetadata, databaseName, mDatabaseId, - mEnforcingQuota, mInPrivateBrowsing); + persistenceType, mOriginMetadata, databaseName, mDatabaseId.ref(), + mDatabaseFilePath.ref(), mEnforcingQuota, mInPrivateBrowsing); } const auto maybeKey = @@ -16237,7 +16590,7 @@ nsresult DeleteDatabaseOp::DoDatabaseWork() { const auto& databaseFilePath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); - MOZ_ASSERT(databaseFilePath == mDatabaseFilePath); + MOZ_ASSERT(databaseFilePath == mDatabaseFilePath.ref()); } #endif @@ -16271,7 +16624,7 @@ nsresult DeleteDatabaseOp::BeginVersionChange() { } DatabaseActorInfo* info; - if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) { + if (gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)) { MOZ_ASSERT(!info->mWaitingFactoryOp); nsresult rv = @@ -16345,9 +16698,9 @@ void DeleteDatabaseOp::SendResults() { MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); DebugOnly<DatabaseActorInfo*> info = nullptr; - MOZ_ASSERT_IF( - gLiveDatabaseHashtable && gLiveDatabaseHashtable->Get(mDatabaseId, &info), - !info->mWaitingFactoryOp); + MOZ_ASSERT_IF(mDatabaseId.isSome() && gLiveDatabaseHashtable && + gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info), + !info->mWaitingFactoryOp); if (!IsActorDestroyed()) { FactoryRequestResponse response; @@ -16428,7 +16781,7 @@ void DeleteDatabaseOp::VersionChangeOp::RunOnOwningThread() { // Inform all the other databases that they are now invalidated. That // should remove the previous metadata from our table. - if (gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId, &info)) { + if (gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId.ref(), &info)) { MOZ_ASSERT(!info->mLiveDatabases.IsEmpty()); MOZ_ASSERT(!info->mWaitingFactoryOp); @@ -16454,7 +16807,7 @@ void DeleteDatabaseOp::VersionChangeOp::RunOnOwningThread() { database->Invalidate(); } - MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId)); + MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId.ref())); } } } @@ -16491,21 +16844,257 @@ 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::DoDirectoryWork() { + AssertIsOnIOThread(); + MOZ_ASSERT(mState == State::DirectoryWorkOpen); + + // This state (DirectoryWorkOpen) runs immediately on the I/O thread, before + // waiting for existing factory operations to complete (at which point + // DoDatabaseWork will be invoked). To match the spec, we must snapshot the + // current state of any databases that are being created (version = 0) or + // upgraded (version >= 1) now. If we only sampled these values in + // DoDatabaseWork, we would only see their post-creation/post-upgrade + // versions, which would be incorrect. + + IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get(); + MOZ_ASSERT(idm); + + const auto& fileManagers = + idm->GetFileManagers(mPersistenceType, mOriginMetadata.mOrigin); + + for (const auto& fileManager : fileManagers) { + auto& metadata = + mDatabaseMetadataTable.LookupOrInsert(fileManager->DatabaseFilePath()); + metadata.name() = fileManager->DatabaseName(); + metadata.version() = fileManager->DatabaseVersion(); + } + + // Must set this before dispatching otherwise we will race with the IO thread. + mState = State::DirectoryWorkDone; + + 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((["aManager, 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, + (["aManager, 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); + + // Use the snapshotted values from DoDirectoryWork which correctly + // snapshotted the state of any pending creations/upgrades. This does mean + // that we need to skip reporting databases that had a version of 0 at that + // time because they were still being created. In the event that any other + // creation or upgrade requests are made after our operation is created, + // this operation will block those, so it's not possible for this set of + // data to get out of sync. The snapshotting (using cached database name + // and version in DatabaseFileManager) also guarantees that we are not + // touching the SQLite database here on the QuotaManager I/O thread which + // is already open on the connection thread. + + auto metadata = mDatabaseMetadataTable.Lookup(path); + if (metadata) { + if (metadata->version() != 0) { + mDatabaseMetadataArray.AppendElement(DatabaseMetadata( + metadata->name(), metadata->version(), mPersistenceType)); + } + + continue; + } + + // Since the database is not already open (there was no DatabaseFileManager + // for snapshotting in DoDirectoryWork which could provide us with the + // database name and version without needing to open the SQLite database), + // 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) + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId) : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(), aTransaction->GetLoggingInfo()->NextRequestSN()), mTransaction(WrapNotNull(std::move(aTransaction))), + mRequestId(aRequestId), mTransactionIsAborted((*mTransaction)->IsAborted()), mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) { MOZ_ASSERT(LoggingSerialNumber()); } TransactionDatabaseOperationBase::TransactionDatabaseOperationBase( - SafeRefPtr<TransactionBase> aTransaction, uint64_t aLoggingSerialNumber) + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, + uint64_t aLoggingSerialNumber) : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(), aLoggingSerialNumber), mTransaction(WrapNotNull(std::move(aTransaction))), + mRequestId(aRequestId), mTransactionIsAborted((*mTransaction)->IsAborted()), mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) {} @@ -16729,7 +17318,7 @@ void TransactionDatabaseOperationBase::SendPreprocessInfoOrResults( mWaitingForContinue = true; } else { if (mLoggingSerialNumber) { - (*mTransaction)->NoteFinishedRequest(mLoggingSerialNumber, ResultCode()); + (*mTransaction)->NoteFinishedRequest(mRequestId, ResultCode()); } Cleanup(); @@ -18199,8 +18788,9 @@ mozilla::ipc::IPCResult NormalTransactionOp::RecvContinue( } ObjectStoreAddOrPutRequestOp::ObjectStoreAddOrPutRequestOp( - SafeRefPtr<TransactionBase> aTransaction, RequestParams&& aParams) - : NormalTransactionOp(std::move(aTransaction)), + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, + RequestParams&& aParams) + : NormalTransactionOp(std::move(aTransaction), aRequestId), mParams( std::move(aParams.type() == RequestParams::TObjectStoreAddParams ? aParams.get_ObjectStoreAddParams().commonParams() @@ -18429,6 +19019,11 @@ nsresult ObjectStoreAddOrPutRequestOp::DoDatabaseWork( } QM_TRY(key.SetFromInteger(autoIncrementNum)); + + // Update index keys if primary key is preserved in child. + for (auto& updateInfo : mParams.indexUpdateInfos()) { + updateInfo.value().MaybeUpdateAutoIncrementKey(autoIncrementNum); + } } else if (key.IsFloat()) { double numericKey = key.ToFloat(); numericKey = std::min(numericKey, double(1LL << 53)); @@ -18715,9 +19310,9 @@ ObjectStoreAddOrPutRequestOp::SCInputStream::IsNonBlocking(bool* _retval) { } ObjectStoreGetRequestOp::ObjectStoreGetRequestOp( - SafeRefPtr<TransactionBase> aTransaction, const RequestParams& aParams, - bool aGetAll) - : NormalTransactionOp(std::move(aTransaction)), + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, + const RequestParams& aParams, bool aGetAll) + : NormalTransactionOp(std::move(aTransaction), aRequestId), mObjectStoreId(aGetAll ? aParams.get_ObjectStoreGetAllParams().objectStoreId() : aParams.get_ObjectStoreGetParams().objectStoreId()), @@ -18887,9 +19482,9 @@ void ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse, } ObjectStoreGetKeyRequestOp::ObjectStoreGetKeyRequestOp( - SafeRefPtr<TransactionBase> aTransaction, const RequestParams& aParams, - bool aGetAll) - : NormalTransactionOp(std::move(aTransaction)), + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, + const RequestParams& aParams, bool aGetAll) + : NormalTransactionOp(std::move(aTransaction), aRequestId), mObjectStoreId( aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().objectStoreId() : aParams.get_ObjectStoreGetKeyParams().objectStoreId()), @@ -18975,9 +19570,9 @@ void ObjectStoreGetKeyRequestOp::GetResponse(RequestResponse& aResponse, } ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp( - SafeRefPtr<TransactionBase> aTransaction, + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, const ObjectStoreDeleteParams& aParams) - : NormalTransactionOp(std::move(aTransaction)), + : NormalTransactionOp(std::move(aTransaction), aRequestId), mParams(aParams), mObjectStoreMayHaveIndexes(false) { AssertIsOnBackgroundThread(); @@ -19036,9 +19631,9 @@ nsresult ObjectStoreDeleteRequestOp::DoDatabaseWork( } ObjectStoreClearRequestOp::ObjectStoreClearRequestOp( - SafeRefPtr<TransactionBase> aTransaction, + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, const ObjectStoreClearParams& aParams) - : NormalTransactionOp(std::move(aTransaction)), + : NormalTransactionOp(std::move(aTransaction), aRequestId), mParams(aParams), mObjectStoreMayHaveIndexes(false) { AssertIsOnBackgroundThread(); @@ -19209,8 +19804,9 @@ SafeRefPtr<FullIndexMetadata> IndexRequestOpBase::IndexMetadataForParams( } IndexGetRequestOp::IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction, + const int64_t aRequestId, const RequestParams& aParams, bool aGetAll) - : IndexRequestOpBase(std::move(aTransaction), aParams), + : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams), mDatabase(Transaction().GetDatabasePtr()), mOptionalKeyRange(aGetAll ? aParams.get_IndexGetAllParams().optionalKeyRange() @@ -19339,9 +19935,9 @@ void IndexGetRequestOp::GetResponse(RequestResponse& aResponse, } IndexGetKeyRequestOp::IndexGetKeyRequestOp( - SafeRefPtr<TransactionBase> aTransaction, const RequestParams& aParams, - bool aGetAll) - : IndexRequestOpBase(std::move(aTransaction), aParams), + SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, + const RequestParams& aParams, bool aGetAll) + : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams), mOptionalKeyRange( aGetAll ? aParams.get_IndexGetAllKeysParams().optionalKeyRange() : Some(aParams.get_IndexGetKeyParams().keyRange())), diff --git a/dom/indexedDB/ActorsParentCommon.cpp b/dom/indexedDB/ActorsParentCommon.cpp index 1b92e15dad..5f25bd8ad6 100644 --- a/dom/indexedDB/ActorsParentCommon.cpp +++ b/dom/indexedDB/ActorsParentCommon.cpp @@ -66,7 +66,7 @@ class nsIFile; namespace mozilla::dom::indexedDB { -static_assert(SNAPPY_VERSION == 0x010109); +static_assert(SNAPPY_VERSION == 0x010200); using mozilla::ipc::IsOnBackgroundThread; diff --git a/dom/indexedDB/DatabaseFileManager.h b/dom/indexedDB/DatabaseFileManager.h index 62d11e95a5..fdfea06ba1 100644 --- a/dom/indexedDB/DatabaseFileManager.h +++ b/dom/indexedDB/DatabaseFileManager.h @@ -31,12 +31,15 @@ class DatabaseFileManager final const quota::OriginMetadata mOriginMetadata; const nsString mDatabaseName; const nsCString mDatabaseID; + const nsString mDatabaseFilePath; RefPtr<IndexedDBCipherKeyManager> mCipherKeyManager; LazyInitializedOnce<const nsString> mDirectoryPath; LazyInitializedOnce<const nsString> mJournalDirectoryPath; + uint64_t mDatabaseVersion; + const bool mEnforcingQuota; const bool mIsInPrivateBrowsingMode; @@ -68,7 +71,8 @@ class DatabaseFileManager final DatabaseFileManager(PersistenceType aPersistenceType, const quota::OriginMetadata& aOriginMetadata, const nsAString& aDatabaseName, - const nsCString& aDatabaseID, bool aEnforcingQuota, + const nsCString& aDatabaseID, + const nsAString& aDatabaseFilePath, bool aEnforcingQuota, bool aIsInPrivateBrowsingMode); PersistenceType Type() const { return mPersistenceType; } @@ -83,6 +87,14 @@ class DatabaseFileManager final const nsCString& DatabaseID() const { return mDatabaseID; } + const nsAString& DatabaseFilePath() const { return mDatabaseFilePath; } + + uint64_t DatabaseVersion() const { return mDatabaseVersion; } + + void UpdateDatabaseVersion(uint64_t aDatabaseVersion) { + mDatabaseVersion = aDatabaseVersion; + } + IndexedDBCipherKeyManager& MutableCipherKeyManagerRef() const { MOZ_ASSERT(mIsInPrivateBrowsingMode); MOZ_ASSERT(mCipherKeyManager); diff --git a/dom/indexedDB/IDBCursor.cpp b/dom/indexedDB/IDBCursor.cpp index 2fb6e98715..65234bf792 100644 --- a/dom/indexedDB/IDBCursor.cpp +++ b/dom/indexedDB/IDBCursor.cpp @@ -417,7 +417,8 @@ void IDBTypedCursor<CursorType>::Continue(JSContext* const aCx, IDB_LOG_STRINGIFY(key)); } - GetTypedBackgroundActorRef().SendContinueInternal(ContinueParams(key), mData); + GetTypedBackgroundActorRef().SendContinueInternal( + mTransaction->NextRequestId(), ContinueParams(key), mData); mContinueCalled = true; } @@ -523,6 +524,7 @@ void IDBTypedCursor<CursorType>::ContinuePrimaryKey( IDB_LOG_STRINGIFY(key), IDB_LOG_STRINGIFY(primaryKey)); GetTypedBackgroundActorRef().SendContinueInternal( + mTransaction->NextRequestId(), ContinuePrimaryKeyParams(key, primaryKey), mData); mContinueCalled = true; @@ -573,8 +575,8 @@ void IDBTypedCursor<CursorType>::Advance(const uint32_t aCount, IDB_LOG_STRINGIFY(mSource), IDB_LOG_STRINGIFY(mDirection), aCount); } - GetTypedBackgroundActorRef().SendContinueInternal(AdvanceParams(aCount), - mData); + GetTypedBackgroundActorRef().SendContinueInternal( + mTransaction->NextRequestId(), AdvanceParams(aCount), mData); mContinueCalled = true; } diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index 4bbc0fc68a..c1a70e9401 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -20,6 +20,7 @@ #include "MainThreadUtils.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Services.h" +#include "mozilla/dom/IDBTransactionBinding.h" #include "mozilla/storage.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/BindingDeclarations.h" @@ -474,7 +475,8 @@ void IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv) { RefPtr<IDBTransaction> IDBDatabase::Transaction( JSContext* aCx, const StringOrStringSequence& aStoreNames, - IDBTransactionMode aMode, ErrorResult& aRv) { + IDBTransactionMode aMode, const IDBTransactionOptions& aOptions, + ErrorResult& aRv) { AssertIsOnOwningThread(); if ((aMode == IDBTransactionMode::Readwriteflush || @@ -598,8 +600,26 @@ RefPtr<IDBTransaction> IDBDatabase::Transaction( MOZ_CRASH("Unknown mode!"); } + auto durability = IDBTransaction::Durability::Default; + if (aOptions.IsAnyMemberPresent()) { + switch (aOptions.mDurability) { + case mozilla::dom::IDBTransactionDurability::Default: + durability = IDBTransaction::Durability::Default; + break; + case mozilla::dom::IDBTransactionDurability::Strict: + durability = IDBTransaction::Durability::Strict; + break; + case mozilla::dom::IDBTransactionDurability::Relaxed: + durability = IDBTransaction::Durability::Relaxed; + break; + + default: + MOZ_CRASH("Unknown durability hint!"); + } + } + SafeRefPtr<IDBTransaction> transaction = - IDBTransaction::Create(aCx, this, sortedStoreNames, mode); + IDBTransaction::Create(aCx, this, sortedStoreNames, mode, durability); if (NS_WARN_IF(!transaction)) { IDB_REPORT_INTERNAL_ERR(); MOZ_ASSERT(!NS_IsMainThread(), @@ -617,7 +637,7 @@ RefPtr<IDBTransaction> IDBDatabase::Transaction( IDB_LOG_STRINGIFY(*transaction)); if (!mBackgroundActor->SendPBackgroundIDBTransactionConstructor( - actor, sortedStoreNames, mode)) { + actor, sortedStoreNames, mode, durability)) { IDB_REPORT_INTERNAL_ERR(); aRv.ThrowUnknownError("Failed to create IndexedDB transaction"); return nullptr; diff --git a/dom/indexedDB/IDBDatabase.h b/dom/indexedDB/IDBDatabase.h index 3f7c4aa63c..6d96aaa35d 100644 --- a/dom/indexedDB/IDBDatabase.h +++ b/dom/indexedDB/IDBDatabase.h @@ -166,7 +166,8 @@ class IDBDatabase final : public DOMEventTargetHelper { // This will be called from the DOM. [[nodiscard]] RefPtr<IDBTransaction> Transaction( JSContext* aCx, const StringOrStringSequence& aStoreNames, - IDBTransactionMode aMode, ErrorResult& aRv); + IDBTransactionMode aMode, const IDBTransactionOptions& aOptions, + ErrorResult& aRv); IMPL_EVENT_HANDLER(abort) IMPL_EVENT_HANDLER(close) diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index 81dce07d7e..51d4c4df23 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -15,8 +15,9 @@ #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/IDBFactoryBinding.h" -#include "mozilla/dom/quota/PersistenceType.h" +#include "mozilla/dom/Promise.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" @@ -378,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) < @@ -443,6 +470,71 @@ RefPtr<IDBOpenDBRequest> IDBFactory::DeleteDatabase( /* aDeleting */ true, aCallerType, aRv); } +already_AddRefed<Promise> IDBFactory::Databases(JSContext* const aCx) { + RefPtr<Promise> promise = Promise::CreateInfallible(GetOwnerGlobal()); + + // 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(); + } + + 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(); +} + int16_t IDBFactory::Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst, JS::Handle<JS::Value> aSecond, ErrorResult& aRv) { Key first, second; @@ -512,6 +604,63 @@ RefPtr<IDBOpenDBRequest> IDBFactory::DeleteForPrincipal( /* aDeleting */ true, aGuarantee, aRv); } +nsresult IDBFactory::EnsureBackgroundActor() { + if (mBackgroundActor) { + return NS_OK; + } + + BackgroundChildImpl::ThreadLocal* threadLocal = + BackgroundChildImpl::GetThreadLocalForCurrentThread(); + + UniquePtr<ThreadLocal> newIDBThreadLocal; + ThreadLocal* idbThreadLocal; + + if (threadLocal && threadLocal->mIndexedDBThreadLocal) { + idbThreadLocal = threadLocal->mIndexedDBThreadLocal.get(); + } else { + nsCOMPtr<nsIUUIDGenerator> uuidGen = + do_GetService("@mozilla.org/uuid-generator;1"); + MOZ_ASSERT(uuidGen); + + nsID id{}; + MOZ_ALWAYS_SUCCEEDS(uuidGen->GenerateUUIDInPlace(&id)); + + newIDBThreadLocal = WrapUnique(new ThreadLocal(id)); + idbThreadLocal = newIDBThreadLocal.get(); + } + + PBackgroundChild* backgroundActor = + BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!backgroundActor)) { + return NS_ERROR_FAILURE; + } + + { + BackgroundFactoryChild* actor = new BackgroundFactoryChild(*this); + + mBackgroundActor = static_cast<BackgroundFactoryChild*>( + backgroundActor->SendPBackgroundIDBFactoryConstructor( + actor, idbThreadLocal->GetLoggingInfo(), + IndexedDatabaseManager::GetLocale())); + + if (NS_WARN_IF(!mBackgroundActor)) { + return NS_ERROR_FAILURE; + } + } + + if (newIDBThreadLocal) { + if (!threadLocal) { + threadLocal = BackgroundChildImpl::GetThreadLocalForCurrentThread(); + } + MOZ_ASSERT(threadLocal); + MOZ_ASSERT(!threadLocal->mIndexedDBThreadLocal); + + threadLocal->mIndexedDBThreadLocal = std::move(newIDBThreadLocal); + } + + return NS_OK; +} + RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName, const Optional<uint64_t>& aVersion, bool aDeleting, CallerType aCallerType, @@ -590,29 +739,7 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( return nullptr; } - PersistenceType persistenceType; - - bool isPersistent = - principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo; - if (!isPersistent && - principalInfo.type() == PrincipalInfo::TContentPrincipalInfo) { - nsCString origin = - principalInfo.get_ContentPrincipalInfo().originNoSuffix(); - isPersistent = QuotaManager::IsOriginInternal(origin); - } - - const bool isPrivate = - principalInfo.type() == PrincipalInfo::TContentPrincipalInfo && - principalInfo.get_ContentPrincipalInfo().attrs().mPrivateBrowsingId > 0; - - if (isPersistent) { - // Chrome privilege and internal origins always get persistent storage. - persistenceType = PERSISTENCE_TYPE_PERSISTENT; - } else if (isPrivate) { - persistenceType = PERSISTENCE_TYPE_PRIVATE; - } else { - persistenceType = PERSISTENCE_TYPE_DEFAULT; - } + PersistenceType persistenceType = GetPersistenceType(principalInfo); DatabaseMetadata& metadata = commonParams.metadata(); metadata.name() = aName; @@ -627,60 +754,11 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( params = OpenDatabaseRequestParams(commonParams); } - if (!mBackgroundActor) { - BackgroundChildImpl::ThreadLocal* threadLocal = - BackgroundChildImpl::GetThreadLocalForCurrentThread(); - - UniquePtr<ThreadLocal> newIDBThreadLocal; - ThreadLocal* idbThreadLocal; - - if (threadLocal && threadLocal->mIndexedDBThreadLocal) { - idbThreadLocal = threadLocal->mIndexedDBThreadLocal.get(); - } else { - nsCOMPtr<nsIUUIDGenerator> uuidGen = - do_GetService("@mozilla.org/uuid-generator;1"); - MOZ_ASSERT(uuidGen); - - nsID id; - MOZ_ALWAYS_SUCCEEDS(uuidGen->GenerateUUIDInPlace(&id)); - - newIDBThreadLocal = WrapUnique(new ThreadLocal(id)); - idbThreadLocal = newIDBThreadLocal.get(); - } - - PBackgroundChild* backgroundActor = - BackgroundChild::GetOrCreateForCurrentThread(); - if (NS_WARN_IF(!backgroundActor)) { - IDB_REPORT_INTERNAL_ERR(); - aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - return nullptr; - } - - { - BackgroundFactoryChild* actor = new BackgroundFactoryChild(*this); - - mBackgroundActor = static_cast<BackgroundFactoryChild*>( - backgroundActor->SendPBackgroundIDBFactoryConstructor( - actor, idbThreadLocal->GetLoggingInfo(), - IndexedDatabaseManager::GetLocale())); - - if (NS_WARN_IF(!mBackgroundActor)) { - mBackgroundActorFailed = true; - IDB_REPORT_INTERNAL_ERR(); - aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - return nullptr; - } - } - - if (newIDBThreadLocal) { - if (!threadLocal) { - threadLocal = BackgroundChildImpl::GetThreadLocalForCurrentThread(); - } - MOZ_ASSERT(threadLocal); - MOZ_ASSERT(!threadLocal->mIndexedDBThreadLocal); - - threadLocal->mIndexedDBThreadLocal = std::move(newIDBThreadLocal); - } + nsresult rv = EnsureBackgroundActor(); + if (NS_WARN_IF(NS_FAILED(rv))) { + IDB_REPORT_INTERNAL_ERR(); + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + return nullptr; } RefPtr<IDBOpenDBRequest> request = IDBOpenDBRequest::Create( @@ -704,7 +782,7 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( IDB_LOG_STRINGIFY(aVersion)); } - nsresult rv = InitiateRequest(WrapNotNull(request), params); + rv = InitiateRequest(WrapNotNull(request), params); if (NS_WARN_IF(NS_FAILED(rv))) { IDB_REPORT_INTERNAL_ERR(); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); diff --git a/dom/indexedDB/IDBFactory.h b/dom/indexedDB/IDBFactory.h index 488f885ae7..7139b26f9c 100644 --- a/dom/indexedDB/IDBFactory.h +++ b/dom/indexedDB/IDBFactory.h @@ -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" @@ -59,6 +60,7 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache { UniquePtr<PrincipalInfo> mPrincipalInfo; + // TODO: Unused, remove me! nsCOMPtr<nsIGlobalObject> mGlobal; // This will only be set if the factory belongs to a window in a child @@ -96,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 { @@ -157,6 +162,8 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache { JSContext* aCx, const nsAString& aName, const IDBOpenDBOptions& aOptions, CallerType aCallerType, ErrorResult& aRv); + already_AddRefed<Promise> Databases(JSContext* aCx); + int16_t Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst, JS::Handle<JS::Value> aSecond, ErrorResult& aRv); @@ -194,6 +201,8 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache { static nsresult AllowedForWindowInternal(nsPIDOMWindowInner* aWindow, nsCOMPtr<nsIPrincipal>* aPrincipal); + nsresult EnsureBackgroundActor(); + [[nodiscard]] RefPtr<IDBOpenDBRequest> OpenInternal( JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName, const Optional<uint64_t>& aVersion, bool aDeleting, diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index e6464d2410..728f10b105 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -410,13 +410,20 @@ RefPtr<IDBObjectStore> IDBObjectStore::Create( void IDBObjectStore::AppendIndexUpdateInfo( const int64_t aIndexID, const KeyPath& aKeyPath, const bool aMultiEntry, const nsCString& aLocale, JSContext* const aCx, JS::Handle<JS::Value> aVal, - nsTArray<IndexUpdateInfo>* const aUpdateInfoArray, ErrorResult* const aRv) { + nsTArray<IndexUpdateInfo>* const aUpdateInfoArray, + const VoidOrObjectStoreKeyPathString& aAutoIncrementedObjectStoreKeyPath, + ErrorResult* const aRv) { // This precondition holds when `aVal` is the result of a structured clone. js::AutoAssertNoContentJS noContentJS(aCx); + static_assert(std::is_same_v<IDBObjectStore::VoidOrObjectStoreKeyPathString, + KeyPath::VoidOrObjectStoreKeyPathString>, + "Inconsistent types"); + if (!aMultiEntry) { Key key; - *aRv = aKeyPath.ExtractKey(aCx, aVal, key); + *aRv = + aKeyPath.ExtractKey(aCx, aVal, key, aAutoIncrementedObjectStoreKeyPath); // If an index's keyPath doesn't match an object, we ignore that object. if (aRv->ErrorCodeIs(NS_ERROR_DOM_INDEXEDDB_DATA_ERR) || key.IsUnset()) { @@ -625,6 +632,21 @@ void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper, const nsTArray<IndexMetadata>& indexes = mSpec->indexes(); const uint32_t idxCount = indexes.Length(); + const auto& autoIncrementedObjectStoreKeyPath = + [this]() -> const nsAString& { + if (AutoIncrement() && GetKeyPath().IsValid()) { + // By https://w3c.github.io/IndexedDB/#database-interface , + // createObjectStore algorithm, step 8, neither arrays nor empty paths + // are allowed for autoincremented object stores. + // See also KeyPath::IsAllowedForObjectStore. + MOZ_ASSERT(GetKeyPath().IsString()); + MOZ_ASSERT(!GetKeyPath().IsEmpty()); + return GetKeyPath().mStrings[0]; + } + + return VoidString(); + }(); + aUpdateInfoArray.SetCapacity(idxCount); // Pretty good estimate for (uint32_t idxIndex = 0; idxIndex < idxCount; idxIndex++) { @@ -632,7 +654,8 @@ void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper, AppendIndexUpdateInfo(metadata.id(), metadata.keyPath(), metadata.multiEntry(), metadata.locale(), aCx, - aValueWrapper.Value(), &aUpdateInfoArray, &aRv); + aValueWrapper.Value(), &aUpdateInfoArray, + autoIncrementedObjectStoreKeyPath, &aRv); if (NS_WARN_IF(aRv.Failed())) { return; } diff --git a/dom/indexedDB/IDBObjectStore.h b/dom/indexedDB/IDBObjectStore.h index dc79fa3616..99bec23327 100644 --- a/dom/indexedDB/IDBObjectStore.h +++ b/dom/indexedDB/IDBObjectStore.h @@ -48,6 +48,7 @@ class IDBObjectStore final : public nsISupports, public nsWrapperCache { using KeyPath = indexedDB::KeyPath; using ObjectStoreSpec = indexedDB::ObjectStoreSpec; using StructuredCloneReadInfoChild = indexedDB::StructuredCloneReadInfoChild; + using VoidOrObjectStoreKeyPathString = nsAString; // For AddOrPut() and DeleteInternal(). // TODO Consider removing this, and making the functions public? @@ -98,11 +99,12 @@ class IDBObjectStore final : public nsISupports, public nsWrapperCache { [[nodiscard]] static RefPtr<IDBObjectStore> Create( SafeRefPtr<IDBTransaction> aTransaction, ObjectStoreSpec& aSpec); - static void AppendIndexUpdateInfo(int64_t aIndexID, const KeyPath& aKeyPath, - bool aMultiEntry, const nsCString& aLocale, - JSContext* aCx, JS::Handle<JS::Value> aVal, - nsTArray<IndexUpdateInfo>* aUpdateInfoArray, - ErrorResult* aRv); + static void AppendIndexUpdateInfo( + int64_t aIndexID, const KeyPath& aKeyPath, bool aMultiEntry, + const nsCString& aLocale, JSContext* aCx, JS::Handle<JS::Value> aVal, + nsTArray<IndexUpdateInfo>* aUpdateInfoArray, + const VoidOrObjectStoreKeyPathString& aAutoIncrementedObjectStoreKeyPath, + ErrorResult* aRv); static void ClearCloneReadInfo( indexedDB::StructuredCloneReadInfoChild& aReadInfo); diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp index d984bcacdf..b35bea6eff 100644 --- a/dom/indexedDB/IDBTransaction.cpp +++ b/dom/indexedDB/IDBTransaction.cpp @@ -89,8 +89,9 @@ auto IDBTransaction::DoWithTransactionChild(const Func& aFunc) const { IDBTransaction::IDBTransaction(IDBDatabase* const aDatabase, const nsTArray<nsString>& aObjectStoreNames, - const Mode aMode, nsString aFilename, - const uint32_t aLineNo, const uint32_t aColumn, + const Mode aMode, const Durability aDurability, + nsString aFilename, const uint32_t aLineNo, + const uint32_t aColumn, CreatedFromFactoryFunction /*aDummy*/) : DOMEventTargetHelper(aDatabase), mDatabase(aDatabase), @@ -98,12 +99,14 @@ IDBTransaction::IDBTransaction(IDBDatabase* const aDatabase, mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode)), mNextObjectStoreId(0), mNextIndexId(0), + mNextRequestId(0), mAbortCode(NS_OK), mPendingRequestCount(0), mFilename(std::move(aFilename)), mLineNo(aLineNo), mColumn(aColumn), mMode(aMode), + mDurability(aDurability), mRegistered(false), mNotedActiveTransaction(false) { MOZ_ASSERT(aDatabase); @@ -177,9 +180,11 @@ SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange( nsString filename; uint32_t lineNo, column; aOpenRequest->GetCallerLocation(filename, &lineNo, &column); + // XXX: What should we have as durability hint here? auto transaction = MakeSafeRefPtr<IDBTransaction>( aDatabase, emptyObjectStoreNames, Mode::VersionChange, - std::move(filename), lineNo, column, CreatedFromFactoryFunction{}); + Durability::Default, std::move(filename), lineNo, column, + CreatedFromFactoryFunction{}); transaction->NoteActiveTransaction(); @@ -196,7 +201,8 @@ SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange( // static SafeRefPtr<IDBTransaction> IDBTransaction::Create( JSContext* const aCx, IDBDatabase* const aDatabase, - const nsTArray<nsString>& aObjectStoreNames, const Mode aMode) { + const nsTArray<nsString>& aObjectStoreNames, const Mode aMode, + const Durability aDurability) { MOZ_ASSERT(aDatabase); aDatabase->AssertIsOnOwningThread(); MOZ_ASSERT(!aObjectStoreNames.IsEmpty()); @@ -207,8 +213,8 @@ SafeRefPtr<IDBTransaction> IDBTransaction::Create( uint32_t lineNo, column; IDBRequest::CaptureCaller(aCx, filename, &lineNo, &column); auto transaction = MakeSafeRefPtr<IDBTransaction>( - aDatabase, aObjectStoreNames, aMode, std::move(filename), lineNo, column, - CreatedFromFactoryFunction{}); + aDatabase, aObjectStoreNames, aMode, aDurability, std::move(filename), + lineNo, column, CreatedFromFactoryFunction{}); if (!NS_IsMainThread()) { WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate(); @@ -287,8 +293,9 @@ BackgroundRequestChild* IDBTransaction::StartRequest( BackgroundRequestChild* const actor = new BackgroundRequestChild(std::move(aRequest)); - DoWithTransactionChild([actor, &aParams](auto& transactionChild) { - transactionChild.SendPBackgroundIDBRequestConstructor(actor, aParams); + DoWithTransactionChild([this, actor, &aParams](auto& transactionChild) { + transactionChild.SendPBackgroundIDBRequestConstructor( + actor, NextRequestId(), aParams); }); // Balanced in BackgroundRequestChild::Recv__delete__(). @@ -302,8 +309,9 @@ void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor, AssertIsOnOwningThread(); MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); - DoWithTransactionChild([&aBackgroundActor, &aParams](auto& actor) { - actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor, aParams); + DoWithTransactionChild([this, &aBackgroundActor, &aParams](auto& actor) { + actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor, + NextRequestId(), aParams); }); // Balanced in BackgroundCursorChild::RecvResponse(). @@ -383,15 +391,16 @@ void IDBTransaction::SendCommit(const bool aAutoCommit) { LoggingSerialNumber(), requestSerialNumber, aAutoCommit ? "automatically" : "explicitly"); - const auto lastRequestSerialNumber = - [this, aAutoCommit, - requestSerialNumber]() -> Maybe<decltype(requestSerialNumber)> { + const int64_t requestId = NextRequestId(); + + const auto lastRequestId = [this, aAutoCommit, + requestId]() -> Maybe<decltype(requestId)> { if (aAutoCommit) { return Nothing(); } - // In case of an explicit commit, we need to note the serial number of the - // last request to check if a request submitted before the commit request + // In case of an explicit commit, we need to note the id of the last + // request to check if a request submitted before the commit request // failed. If we are currently in an event handler for a request on this // transaction, ignore this request. This is used to synchronize the // transaction's committing state with the parent side, to abort the @@ -405,15 +414,13 @@ void IDBTransaction::SendCommit(const bool aAutoCommit) { const bool dispatchingEventForThisTransaction = maybeCurrentTransaction && &maybeCurrentTransaction.ref() == this; - return Some(requestSerialNumber - ? (requestSerialNumber - - (dispatchingEventForThisTransaction ? 0 : 1)) + return Some(requestId + ? (requestId - (dispatchingEventForThisTransaction ? 0 : 1)) : 0); }(); - DoWithTransactionChild([lastRequestSerialNumber](auto& actor) { - actor.SendCommit(lastRequestSerialNumber); - }); + DoWithTransactionChild( + [lastRequestId](auto& actor) { actor.SendCommit(lastRequestId); }); mSentCommitOrAbort.Flip(); } @@ -817,6 +824,12 @@ int64_t IDBTransaction::NextIndexId() { return mNextIndexId++; } +int64_t IDBTransaction::NextRequestId() { + AssertIsOnOwningThread(); + + return mNextRequestId++; +} + void IDBTransaction::InvalidateCursorCaches() { AssertIsOnOwningThread(); @@ -869,6 +882,24 @@ IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const { } } +IDBTransactionDurability IDBTransaction::GetDurability(ErrorResult& aRv) const { + AssertIsOnOwningThread(); + + switch (mDurability) { + case Durability::Default: + return IDBTransactionDurability::Default; + + case Durability::Strict: + return IDBTransactionDurability::Strict; + + case Durability::Relaxed: + return IDBTransactionDurability::Relaxed; + + default: + MOZ_CRASH("Bad mode!"); + } +} + DOMException* IDBTransaction::GetError() const { AssertIsOnOwningThread(); diff --git a/dom/indexedDB/IDBTransaction.h b/dom/indexedDB/IDBTransaction.h index 65fefddfe5..9feafb3c68 100644 --- a/dom/indexedDB/IDBTransaction.h +++ b/dom/indexedDB/IDBTransaction.h @@ -63,6 +63,15 @@ class IDBTransaction final Invalid }; + enum struct Durability { + Default = 0, + Strict, + Relaxed, + + // Only needed for IPC serialization helper, should never be used in code. + Invalid + }; + enum struct ReadyState { Active, Inactive, Committing, Finished }; private: @@ -90,6 +99,11 @@ class IDBTransaction final int64_t mNextObjectStoreId; int64_t mNextIndexId; + // Request ids are issued starting from 0 and incremented by one as we send + // actor creation messages to the parent process. Used to support the + // explicit commit() request. + int64_t mNextRequestId; + nsresult mAbortCode; ///< The result that caused the transaction to be ///< aborted, or NS_OK if not aborted. ///< NS_ERROR_DOM_INDEXEDDB_ABORT_ERR indicates that the @@ -108,6 +122,7 @@ class IDBTransaction final ReadyState mReadyState = ReadyState::Active; FlippedOnce<false> mStarted; const Mode mMode; + const Durability mDurability; bool mRegistered; ///< Whether mDatabase->RegisterTransaction() has been ///< called (which may not be the case if construction was @@ -130,7 +145,8 @@ class IDBTransaction final [[nodiscard]] static SafeRefPtr<IDBTransaction> Create( JSContext* aCx, IDBDatabase* aDatabase, - const nsTArray<nsString>& aObjectStoreNames, Mode aMode); + const nsTArray<nsString>& aObjectStoreNames, Mode aMode, + Durability aDurability); static Maybe<IDBTransaction&> MaybeCurrent(); @@ -232,6 +248,11 @@ class IDBTransaction final return mMode; } + Durability GetDurability() const { + AssertIsOnOwningThread(); + return mDurability; + } + uint32_t GetPendingRequestCount() const { return mPendingRequestCount; } IDBDatabase* Database() const { @@ -280,6 +301,9 @@ class IDBTransaction final // Only for Mode::VersionChange transactions. int64_t NextIndexId(); + // See the comment for mNextRequestId. + int64_t NextRequestId(); + void InvalidateCursorCaches(); void RegisterCursor(IDBCursor& aCursor); void UnregisterCursor(IDBCursor& aCursor); @@ -299,6 +323,8 @@ class IDBTransaction final IDBTransactionMode GetMode(ErrorResult& aRv) const; + IDBTransactionDurability GetDurability(ErrorResult& aRv) const; + DOMException* GetError() const; [[nodiscard]] RefPtr<IDBObjectStore> ObjectStore(const nsAString& aName, @@ -323,8 +349,8 @@ class IDBTransaction final public: IDBTransaction(IDBDatabase* aDatabase, const nsTArray<nsString>& aObjectStoreNames, Mode aMode, - nsString aFilename, uint32_t aLineNo, uint32_t aColumn, - CreatedFromFactoryFunction aDummy); + Durability aDurability, nsString aFilename, uint32_t aLineNo, + uint32_t aColumn, CreatedFromFactoryFunction aDummy); private: ~IDBTransaction(); diff --git a/dom/indexedDB/IndexedDatabase.cpp b/dom/indexedDB/IndexedDatabase.cpp index a73d9fbf5a..538aea5280 100644 --- a/dom/indexedDB/IndexedDatabase.cpp +++ b/dom/indexedDB/IndexedDatabase.cpp @@ -391,7 +391,8 @@ JSObject* CommonStructuredCloneReadCallback( !StructuredCloneHolder::ReadString(aReader, value)) { return nullptr; } - params->Append(key, value); + params->Append(NS_ConvertUTF16toUTF8(key), + NS_ConvertUTF16toUTF8(value)); } if (!WrapAsJSObject(aCx, params, &result)) { diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index 0558c38826..e63dd793cf 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -67,6 +67,13 @@ class FileManagerInfo { [[nodiscard]] SafeRefPtr<DatabaseFileManager> GetFileManager( PersistenceType aPersistenceType, const nsAString& aName) const; + [[nodiscard]] SafeRefPtr<DatabaseFileManager> + GetFileManagerByDatabaseFilePath(PersistenceType aPersistenceType, + const nsAString& aDatabaseFilePath) const; + + const nsTArray<SafeRefPtr<DatabaseFileManager>>& GetFileManagers( + PersistenceType aPersistenceType) const; + void AddFileManager(SafeRefPtr<DatabaseFileManager> aFileManager); bool HasFileManagers() const { @@ -86,18 +93,18 @@ class FileManagerInfo { const nsAString& aName); private: - nsTArray<SafeRefPtr<DatabaseFileManager> >& GetArray( + nsTArray<SafeRefPtr<DatabaseFileManager>>& GetArray( PersistenceType aPersistenceType); - const nsTArray<SafeRefPtr<DatabaseFileManager> >& GetImmutableArray( + const nsTArray<SafeRefPtr<DatabaseFileManager>>& GetImmutableArray( PersistenceType aPersistenceType) const { return const_cast<FileManagerInfo*>(this)->GetArray(aPersistenceType); } - nsTArray<SafeRefPtr<DatabaseFileManager> > mPersistentStorageFileManagers; - nsTArray<SafeRefPtr<DatabaseFileManager> > mTemporaryStorageFileManagers; - nsTArray<SafeRefPtr<DatabaseFileManager> > mDefaultStorageFileManagers; - nsTArray<SafeRefPtr<DatabaseFileManager> > mPrivateStorageFileManagers; + nsTArray<SafeRefPtr<DatabaseFileManager>> mPersistentStorageFileManagers; + nsTArray<SafeRefPtr<DatabaseFileManager>> mTemporaryStorageFileManagers; + nsTArray<SafeRefPtr<DatabaseFileManager>> mDefaultStorageFileManagers; + nsTArray<SafeRefPtr<DatabaseFileManager>> mPrivateStorageFileManagers; }; } // namespace indexedDB @@ -199,6 +206,13 @@ auto DatabaseNameMatchPredicate(const nsAString* const aName) { }; } +auto DatabaseFilePathMatchPredicate(const nsAString* const aDatabaseFilePath) { + MOZ_ASSERT(aDatabaseFilePath); + return [aDatabaseFilePath](const auto& fileManager) { + return fileManager->DatabaseFilePath() == *aDatabaseFilePath; + }; +} + } // namespace IndexedDatabaseManager::IndexedDatabaseManager() : mBackgroundActor(nullptr) { @@ -476,6 +490,35 @@ SafeRefPtr<DatabaseFileManager> IndexedDatabaseManager::GetFileManager( return info->GetFileManager(aPersistenceType, aDatabaseName); } +SafeRefPtr<DatabaseFileManager> +IndexedDatabaseManager::GetFileManagerByDatabaseFilePath( + PersistenceType aPersistenceType, const nsACString& aOrigin, + const nsAString& aDatabaseFilePath) { + AssertIsOnIOThread(); + + FileManagerInfo* info; + if (!mFileManagerInfos.Get(aOrigin, &info)) { + return nullptr; + } + + return info->GetFileManagerByDatabaseFilePath(aPersistenceType, + aDatabaseFilePath); +} + +const nsTArray<SafeRefPtr<DatabaseFileManager>>& +IndexedDatabaseManager::GetFileManagers(PersistenceType aPersistenceType, + const nsACString& aOrigin) { + AssertIsOnIOThread(); + + FileManagerInfo* info; + if (!mFileManagerInfos.Get(aOrigin, &info)) { + static nsTArray<SafeRefPtr<DatabaseFileManager>> emptyArray; + return emptyArray; + } + + return info->GetFileManagers(aPersistenceType); +} + void IndexedDatabaseManager::AddFileManager( SafeRefPtr<DatabaseFileManager> aFileManager) { AssertIsOnIOThread(); @@ -680,11 +723,34 @@ SafeRefPtr<DatabaseFileManager> FileManagerInfo::GetFileManager( return foundIt != end ? foundIt->clonePtr() : nullptr; } +SafeRefPtr<DatabaseFileManager> +FileManagerInfo::GetFileManagerByDatabaseFilePath( + PersistenceType aPersistenceType, + const nsAString& aDatabaseFilePath) const { + AssertIsOnIOThread(); + + const auto& managers = GetImmutableArray(aPersistenceType); + + const auto end = managers.cend(); + const auto foundIt = + std::find_if(managers.cbegin(), end, + DatabaseFilePathMatchPredicate(&aDatabaseFilePath)); + + return foundIt != end ? foundIt->clonePtr() : nullptr; +} + +const nsTArray<SafeRefPtr<DatabaseFileManager>>& +FileManagerInfo::GetFileManagers(PersistenceType aPersistenceType) const { + AssertIsOnIOThread(); + + return GetImmutableArray(aPersistenceType); +} + void FileManagerInfo::AddFileManager( SafeRefPtr<DatabaseFileManager> aFileManager) { AssertIsOnIOThread(); - nsTArray<SafeRefPtr<DatabaseFileManager> >& managers = + nsTArray<SafeRefPtr<DatabaseFileManager>>& managers = GetArray(aFileManager->Type()); NS_ASSERTION(!managers.Contains(aFileManager), "Adding more than once?!"); @@ -718,7 +784,7 @@ void FileManagerInfo::InvalidateAndRemoveFileManagers( PersistenceType aPersistenceType) { AssertIsOnIOThread(); - nsTArray<SafeRefPtr<DatabaseFileManager> >& managers = + nsTArray<SafeRefPtr<DatabaseFileManager>>& managers = GetArray(aPersistenceType); for (uint32_t i = 0; i < managers.Length(); i++) { @@ -743,7 +809,7 @@ void FileManagerInfo::InvalidateAndRemoveFileManager( } } -nsTArray<SafeRefPtr<DatabaseFileManager> >& FileManagerInfo::GetArray( +nsTArray<SafeRefPtr<DatabaseFileManager>>& FileManagerInfo::GetArray( PersistenceType aPersistenceType) { switch (aPersistenceType) { case PERSISTENCE_TYPE_PERSISTENT: diff --git a/dom/indexedDB/IndexedDatabaseManager.h b/dom/indexedDB/IndexedDatabaseManager.h index 0766143dd6..3d0f467368 100644 --- a/dom/indexedDB/IndexedDatabaseManager.h +++ b/dom/indexedDB/IndexedDatabaseManager.h @@ -99,6 +99,14 @@ class IndexedDatabaseManager final { PersistenceType aPersistenceType, const nsACString& aOrigin, const nsAString& aDatabaseName); + [[nodiscard]] SafeRefPtr<DatabaseFileManager> + GetFileManagerByDatabaseFilePath(PersistenceType aPersistenceType, + const nsACString& aOrigin, + const nsAString& aDatabaseFilePath); + + const nsTArray<SafeRefPtr<DatabaseFileManager>>& GetFileManagers( + PersistenceType aPersistenceType, const nsACString& aOrigin); + void AddFileManager(SafeRefPtr<DatabaseFileManager> aFileManager); void InvalidateAllFileManagers(); diff --git a/dom/indexedDB/Key.cpp b/dom/indexedDB/Key.cpp index 6f96023a54..7e0c607617 100644 --- a/dom/indexedDB/Key.cpp +++ b/dom/indexedDB/Key.cpp @@ -560,6 +560,51 @@ Result<Ok, nsresult> Key::EncodeString(const Span<const T> aInput, #define KEY_MAXIMUM_BUFFER_LENGTH \ ::mozilla::detail::nsTStringLengthStorage<char>::kMax +void Key::ReserveAutoIncrementKey(bool aFirstOfArray) { + // Allocate memory for the new size + uint32_t oldLen = mBuffer.Length(); + char* buffer; + if (!mBuffer.GetMutableData(&buffer, oldLen + 1 + sizeof(double))) { + return; + } + + // Remember the offset of the buffer to be updated later. + mAutoIncrementKeyOffsets.AppendElement(oldLen + 1); + + // Fill the type. + buffer += oldLen; + *(buffer++) = aFirstOfArray ? (eMaxType + eFloat) : eFloat; + + // Fill up with 0xFF to reserve the buffer in fixed size because the encoded + // string could be trimmed if ended with padding zeros. + mozilla::BigEndian::writeUint64(buffer, UINT64_MAX); +} + +void Key::MaybeUpdateAutoIncrementKey(int64_t aKey) { + if (mAutoIncrementKeyOffsets.IsEmpty()) { + return; + } + + for (uint32_t offset : mAutoIncrementKeyOffsets) { + char* buffer; + MOZ_ALWAYS_TRUE(mBuffer.GetMutableData(&buffer)); + buffer += offset; + WriteDoubleToUint64(buffer, double(aKey)); + } + + TrimBuffer(); +} + +void Key::WriteDoubleToUint64(char* aBuffer, double aValue) { + MOZ_ASSERT(aBuffer); + + uint64_t bits = BitwiseCast<uint64_t>(aValue); + const uint64_t signbit = FloatingPoint<double>::kSignBit; + uint64_t number = bits & signbit ? (-bits) : (bits | signbit); + + mozilla::BigEndian::writeUint64(aBuffer, number); +} + template <typename T> Result<Ok, nsresult> Key::EncodeAsString(const Span<const T> aInput, uint8_t aType) { @@ -797,13 +842,8 @@ Result<Ok, nsresult> Key::EncodeNumber(double aFloat, uint8_t aType) { *(buffer++) = aType; - uint64_t bits = BitwiseCast<uint64_t>(aFloat); - // Note: The subtraction from 0 below is necessary to fix - // MSVC build warning C4146 (negating an unsigned value). - const uint64_t signbit = FloatingPoint<double>::kSignBit; - uint64_t number = bits & signbit ? (0 - bits) : (bits | signbit); + WriteDoubleToUint64(buffer, aFloat); - mozilla::BigEndian::writeUint64(buffer, number); return Ok(); } diff --git a/dom/indexedDB/Key.h b/dom/indexedDB/Key.h index d19bc94e53..25ddd3e0b1 100644 --- a/dom/indexedDB/Key.h +++ b/dom/indexedDB/Key.h @@ -25,6 +25,7 @@ class Key { friend struct IPC::ParamTraits<Key>; nsCString mBuffer; + CopyableTArray<uint32_t> mAutoIncrementKeyOffsets; public: enum { @@ -86,7 +87,10 @@ class Key { return Compare(mBuffer, aOther.mBuffer) >= 0; } - void Unset() { mBuffer.SetIsVoid(true); } + void Unset() { + mBuffer.SetIsVoid(true); + mAutoIncrementKeyOffsets.Clear(); + } bool IsUnset() const { return mBuffer.IsVoid(); } @@ -174,6 +178,10 @@ class Key { return 0; } + void ReserveAutoIncrementKey(bool aFirstOfArray); + + void MaybeUpdateAutoIncrementKey(int64_t aKey); + private: class MOZ_STACK_CLASS ArrayValueEncoder; @@ -273,6 +281,8 @@ class Key { template <typename T> nsresult SetFromSource(T* aSource, uint32_t aIndex); + + void WriteDoubleToUint64(char* aBuffer, double aValue); }; } // namespace mozilla::dom::indexedDB diff --git a/dom/indexedDB/KeyPath.cpp b/dom/indexedDB/KeyPath.cpp index 5cad164296..f27ddd007a 100644 --- a/dom/indexedDB/KeyPath.cpp +++ b/dom/indexedDB/KeyPath.cpp @@ -329,8 +329,9 @@ bool KeyPath::AppendStringWithValidation(const nsAString& aString) { return false; } -nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, - Key& aKey) const { +nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey, + const VoidOrObjectStoreKeyPathString& + aAutoIncrementedObjectStoreKeyPath) const { uint32_t len = mStrings.Length(); JS::Rooted<JS::Value> value(aCx); @@ -341,6 +342,17 @@ nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, GetJSValFromKeyPathString(aCx, aValue, mStrings[i], value.address(), DoNotCreateProperties, nullptr, nullptr); if (NS_FAILED(rv)) { + if (!aAutoIncrementedObjectStoreKeyPath.IsVoid() && + mStrings[i].Equals(aAutoIncrementedObjectStoreKeyPath)) { + // We are extracting index keys of an object to be added if + // object store key path for a string key is provided. + // Because the autoIncrement primary key is part of + // this index key but is not defined in |aValue|, so we reserve + // the space here to update the key later in parent. + aKey.ReserveAutoIncrementKey(IsArray() && i == 0); + continue; + } + return rv; } diff --git a/dom/indexedDB/KeyPath.h b/dom/indexedDB/KeyPath.h index 4e1042650f..5fac30ecf8 100644 --- a/dom/indexedDB/KeyPath.h +++ b/dom/indexedDB/KeyPath.h @@ -45,6 +45,8 @@ class KeyPath { KeyPath() : mType(KeyPathType::NonExistent) { MOZ_COUNT_CTOR(KeyPath); } public: + using VoidOrObjectStoreKeyPathString = nsAString; + enum class KeyPathType { NonExistent, String, Array, EndGuard }; void SetType(KeyPathType aType); @@ -76,7 +78,10 @@ class KeyPath { static Result<KeyPath, nsresult> Parse( const Nullable<OwningStringOrStringSequence>& aValue); - nsresult ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const; + nsresult ExtractKey( + JSContext* aCx, const JS::Value& aValue, Key& aKey, + const VoidOrObjectStoreKeyPathString& aAutoIncrementedObjectStoreKeyPath = + VoidString()) const; nsresult ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue, JS::Value* aOutVal) const; diff --git a/dom/indexedDB/PBackgroundIDBCursor.ipdl b/dom/indexedDB/PBackgroundIDBCursor.ipdl index 60c8d2ca7b..e7669f2554 100644 --- a/dom/indexedDB/PBackgroundIDBCursor.ipdl +++ b/dom/indexedDB/PBackgroundIDBCursor.ipdl @@ -87,8 +87,8 @@ protocol PBackgroundIDBCursor parent: async DeleteMe(); - async Continue(CursorRequestParams params, Key currentKey, - Key currentObjectStoreKey); + async Continue(int64_t requestId, CursorRequestParams params, + Key currentKey, Key currentObjectStoreKey); child: async __delete__(); diff --git a/dom/indexedDB/PBackgroundIDBDatabase.ipdl b/dom/indexedDB/PBackgroundIDBDatabase.ipdl index f9262ebadf..88d5e79392 100644 --- a/dom/indexedDB/PBackgroundIDBDatabase.ipdl +++ b/dom/indexedDB/PBackgroundIDBDatabase.ipdl @@ -19,6 +19,9 @@ using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; using mozilla::dom::IDBTransaction::Mode from "mozilla/dom/IDBTransaction.h"; +using mozilla::dom::IDBTransaction::Durability + from "mozilla/dom/IDBTransaction.h"; + namespace mozilla { namespace dom { namespace indexedDB { @@ -41,7 +44,7 @@ parent: async PBackgroundIDBDatabaseFile(IPCBlob blob); - async PBackgroundIDBTransaction(nsString[] objectStoreNames, Mode mode); + async PBackgroundIDBTransaction(nsString[] objectStoreNames, Mode mode, Durability durability); child: async __delete__(); diff --git a/dom/indexedDB/PBackgroundIDBFactory.ipdl b/dom/indexedDB/PBackgroundIDBFactory.ipdl index ef2a88e55b..c838189b2b 100644 --- a/dom/indexedDB/PBackgroundIDBFactory.ipdl +++ b/dom/indexedDB/PBackgroundIDBFactory.ipdl @@ -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__(); diff --git a/dom/indexedDB/PBackgroundIDBTransaction.ipdl b/dom/indexedDB/PBackgroundIDBTransaction.ipdl index 85a9f76265..071e031cb2 100644 --- a/dom/indexedDB/PBackgroundIDBTransaction.ipdl +++ b/dom/indexedDB/PBackgroundIDBTransaction.ipdl @@ -34,9 +34,9 @@ parent: async Commit(int64_t? lastRequest); async Abort(nsresult resultCode); - async PBackgroundIDBCursor(OpenCursorParams params); + async PBackgroundIDBCursor(int64_t requestId, OpenCursorParams params); - async PBackgroundIDBRequest(RequestParams params); + async PBackgroundIDBRequest(int64_t requestId, RequestParams params); child: async __delete__(); diff --git a/dom/indexedDB/PBackgroundIDBVersionChangeTransaction.ipdl b/dom/indexedDB/PBackgroundIDBVersionChangeTransaction.ipdl index e139f0f1c3..c096c28996 100644 --- a/dom/indexedDB/PBackgroundIDBVersionChangeTransaction.ipdl +++ b/dom/indexedDB/PBackgroundIDBVersionChangeTransaction.ipdl @@ -36,9 +36,9 @@ parent: async DeleteIndex(int64_t objectStoreId, int64_t indexId); async RenameIndex(int64_t objectStoreId, int64_t indexId, nsString name); - async PBackgroundIDBCursor(OpenCursorParams params); + async PBackgroundIDBCursor(int64_t requestId, OpenCursorParams params); - async PBackgroundIDBRequest(RequestParams params); + async PBackgroundIDBRequest(int64_t requestId, RequestParams params); child: async __delete__(); diff --git a/dom/indexedDB/SchemaUpgrades.cpp b/dom/indexedDB/SchemaUpgrades.cpp index 234c4c9f04..17230e247f 100644 --- a/dom/indexedDB/SchemaUpgrades.cpp +++ b/dom/indexedDB/SchemaUpgrades.cpp @@ -2863,7 +2863,7 @@ nsresult UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory, auto fileManager = MakeSafeRefPtr<DatabaseFileManager>( PERSISTENCE_TYPE_INVALID, quota::OriginMetadata{}, /* aDatabaseName */ u""_ns, /* aDatabaseID */ ""_ns, - /* aEnforcingQuota */ false, + /* aDatabaseFilePath */ u""_ns, /* aEnforcingQuota */ false, /* aIsInPrivateBrowsingMode */ false); nsresult rv = fileManager->Init(aFMDirectory, aConnection); diff --git a/dom/indexedDB/SerializationHelpers.h b/dom/indexedDB/SerializationHelpers.h index 488c1eb044..a79c6cefb4 100644 --- a/dom/indexedDB/SerializationHelpers.h +++ b/dom/indexedDB/SerializationHelpers.h @@ -31,10 +31,12 @@ struct ParamTraits<mozilla::dom::indexedDB::Key> { static void Write(MessageWriter* aWriter, const paramType& aParam) { WriteParam(aWriter, aParam.mBuffer); + WriteParam(aWriter, aParam.mAutoIncrementKeyOffsets); } static bool Read(MessageReader* aReader, paramType* aResult) { - return ReadParam(aReader, &aResult->mBuffer); + return ReadParam(aReader, &aResult->mBuffer) && + ReadParam(aReader, &aResult->mAutoIncrementKeyOffsets); } }; @@ -72,6 +74,13 @@ struct ParamTraits<mozilla::dom::IDBTransaction::Mode> mozilla::dom::IDBTransaction::Mode::ReadOnly, mozilla::dom::IDBTransaction::Mode::Invalid> {}; +template <> +struct ParamTraits<mozilla::dom::IDBTransaction::Durability> + : public ContiguousEnumSerializer< + mozilla::dom::IDBTransaction::Durability, + mozilla::dom::IDBTransaction::Durability::Default, + mozilla::dom::IDBTransaction::Durability::Invalid> {}; + } // namespace IPC #endif // mozilla_dom_indexeddb_serializationhelpers_h__ diff --git a/dom/indexedDB/test/mochitest-common.toml b/dom/indexedDB/test/mochitest-common.toml index d42228da45..0959f36e13 100644 --- a/dom/indexedDB/test/mochitest-common.toml +++ b/dom/indexedDB/test/mochitest-common.toml @@ -73,6 +73,7 @@ support-files = [ "unit/test_objectStore_remove_values.js", "unit/test_object_identity.js", "unit/test_odd_result_order.js", + "unit/test_open_and_databases.js", "unit/test_open_empty_db.js", "unit/test_open_for_principal.js", "unit/test_open_objectStore.js", @@ -299,6 +300,8 @@ skip-if = [ ["test_odd_result_order.html"] +["test_open_and_databases.html"] + ["test_open_empty_db.html"] ["test_open_for_principal.html"] diff --git a/dom/indexedDB/test/test_open_and_databases.html b/dom/indexedDB/test/test_open_and_databases.html new file mode 100644 index 0000000000..9d0113136e --- /dev/null +++ b/dom/indexedDB/test/test_open_and_databases.html @@ -0,0 +1,19 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Indexed Database Property Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="unit/test_open_and_databases.js"></script> + <script type="text/javascript" src="helpers.js"></script> + +</head> + +<body onload="runTest();"></body> + +</html> diff --git a/dom/indexedDB/test/test_third_party.html b/dom/indexedDB/test/test_third_party.html index ee90de8dac..439f50d754 100644 --- a/dom/indexedDB/test/test_third_party.html +++ b/dom/indexedDB/test/test_third_party.html @@ -25,12 +25,12 @@ { host: "http://sub1.test2.example.org:8000", cookieBehavior: BEHAVIOR_REJECT, expectedResultFrame1: false, expectedResultFrame2: false }, { host: "http://" + window.location.host, cookieBehavior: BEHAVIOR_REJECTFOREIGN, expectedResultFrame1: true, expectedResultFrame2: true }, - { host: "http://example.com", cookieBehavior: BEHAVIOR_REJECTFOREIGN, expectedResultFrame1: false, expectedResultFrame2: true }, - { host: "http://sub1.test2.example.org:8000", cookieBehavior: BEHAVIOR_REJECTFOREIGN, expectedResultFrame1: false, expectedResultFrame2: true }, + { host: "http://example.com", cookieBehavior: BEHAVIOR_REJECTFOREIGN, expectedResultFrame1: false, expectedResultFrame2: false }, + { host: "http://sub1.test2.example.org:8000", cookieBehavior: BEHAVIOR_REJECTFOREIGN, expectedResultFrame1: false, expectedResultFrame2: false }, { host: "http://" + window.location.host, cookieBehavior: BEHAVIOR_LIMITFOREIGN, expectedResultFrame1: true, expectedResultFrame2: true }, - { host: "http://example.com", cookieBehavior: BEHAVIOR_LIMITFOREIGN, expectedResultFrame1: false, expectedResultFrame2: true }, - { host: "http://sub1.test2.example.org:8000", cookieBehavior: BEHAVIOR_LIMITFOREIGN, expectedResultFrame1: false, expectedResultFrame2: true }, + { host: "http://example.com", cookieBehavior: BEHAVIOR_LIMITFOREIGN, expectedResultFrame1: false, expectedResultFrame2: false }, + { host: "http://sub1.test2.example.org:8000", cookieBehavior: BEHAVIOR_LIMITFOREIGN, expectedResultFrame1: false, expectedResultFrame2: false }, ]; const iframe1Path = diff --git a/dom/indexedDB/test/unit/test_connection_idle_maintenance.js b/dom/indexedDB/test/unit/test_connection_idle_maintenance.js index 288f656c65..819b0b61bb 100644 --- a/dom/indexedDB/test/unit/test_connection_idle_maintenance.js +++ b/dom/indexedDB/test/unit/test_connection_idle_maintenance.js @@ -8,7 +8,7 @@ async function testSteps() { // A constant used to deal with small decrease in usage when transactions are // transferred from the WAL file back into the original database, also called // as checkpointing. - const cosmologicalConstant = 31768; + const cosmologicalConstant = 20000; // The length of time that database connections will be held open after all // transactions have completed before doing idle maintenance. diff --git a/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js b/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js index 33dc69b210..5453e4dc4e 100644 --- a/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js +++ b/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js @@ -8,7 +8,7 @@ async function testSteps() { // A constant used to deal with small decrease in usage when transactions are // transferred from the WAL file back into the original database, also called // as checkpointing. - const cosmologicalConstant = 32768; + const cosmologicalConstant = 35000; // The maximum number of threads that can be used for database activity at a // single time. diff --git a/dom/indexedDB/test/unit/test_open_and_databases.js b/dom/indexedDB/test/unit/test_open_and_databases.js new file mode 100644 index 0000000000..b13cba067a --- /dev/null +++ b/dom/indexedDB/test/unit/test_open_and_databases.js @@ -0,0 +1,76 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* exported testSteps */ +async function testSteps() { + const openInfos = [ + { name: "foo-a", version: 1 }, + { name: "foo-b", version: 1 }, + ]; + + info("Creating databases"); + + for (let index = 0; index < openInfos.length; index++) { + const openInfo = openInfos[index]; + + const request = indexedDB.open(openInfo.name, openInfo.version); + + await expectingUpgrade(request); + + const event = await expectingSuccess(request); + + const database = event.target.result; + + database.close(); + } + + info("Getting databases"); + + const databasesPromise = indexedDB.databases(); + + info("Opening databases"); + + const openPromises = []; + + for (let index = 0; index < openInfos.length; index++) { + const openInfo = openInfos[index]; + + const request = indexedDB.open(openInfo.name, openInfo.version); + + const promise = expectingSuccess(request); + + openPromises.push(promise); + } + + info("Waiting for databases operation to complete"); + + const databaseInfos = await databasesPromise; + + info("Verifying databases"); + + is( + databaseInfos.length, + openInfos.length, + "The result of databases() should contain one result per database" + ); + + for (let index = 0; index < openInfos.length; index++) { + const openInfo = openInfos[index]; + + ok( + databaseInfos.some(function (element) { + return ( + element.name === openInfo.name && element.version === openInfo.version + ); + }), + "The result of databases() should be a sequence of the correct names " + + "and versions of all databases for the origin" + ); + } + + info("Waiting for open operations to complete"); + + await Promise.all(openPromises); +} diff --git a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js index f6bd2608ef..840eef7908 100644 --- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js +++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js @@ -56,7 +56,11 @@ if (!this.runTest) { if (testSteps.constructor.name === "AsyncFunction") { // Do run our existing cleanup function that would normally be called by // the generator's call to finishTest(). - registerCleanupFunction(resetTesting); + registerCleanupFunction(function () { + if (SpecialPowers.isMainProcess()) { + resetTesting(); + } + }); add_task(testSteps); @@ -644,7 +648,7 @@ var SpecialPowers = { clearUserPref(prefName) { Services.prefs.clearUserPref(prefName); }, - // Copied (and slightly adjusted) from testing/specialpowers/content/SpecialPowersAPI.jsm + // Copied (and slightly adjusted) from testing/specialpowers/api.js exactGC(callback) { let count = 0; diff --git a/dom/indexedDB/test/unit/xpcshell-shared.toml b/dom/indexedDB/test/unit/xpcshell-shared.toml index ac1183ab2d..f967236a9e 100644 --- a/dom/indexedDB/test/unit/xpcshell-shared.toml +++ b/dom/indexedDB/test/unit/xpcshell-shared.toml @@ -111,6 +111,8 @@ skip-if = ["os == 'android'"] # bug 864843 ["test_odd_result_order.js"] +["test_open_and_databases.js"] + ["test_open_empty_db.js"] ["test_open_for_principal.js"] |