diff options
Diffstat (limited to 'dom/indexedDB/ActorsChild.cpp')
-rw-r--r-- | dom/indexedDB/ActorsChild.cpp | 3495 |
1 files changed, 3495 insertions, 0 deletions
diff --git a/dom/indexedDB/ActorsChild.cpp b/dom/indexedDB/ActorsChild.cpp new file mode 100644 index 0000000000..977b2427f2 --- /dev/null +++ b/dom/indexedDB/ActorsChild.cpp @@ -0,0 +1,3495 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <type_traits> + +#include "ActorsChild.h" +#include "BackgroundChildImpl.h" +#include "IDBDatabase.h" +#include "IDBEvents.h" +#include "IDBFactory.h" +#include "IDBFileHandle.h" +#include "IDBIndex.h" +#include "IDBMutableFile.h" +#include "IDBObjectStore.h" +#include "IDBRequest.h" +#include "IDBTransaction.h" +#include "IndexedDatabase.h" +#include "IndexedDatabaseInlines.h" +#include "IndexedDBCommon.h" +#include "js/Array.h" // JS::NewArrayObject, JS::SetArrayLength +#include "js/Date.h" // JS::NewDateObject, JS::TimeClip +#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty +#include <mozIRemoteLazyInputStream.h> +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/Maybe.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/Encoding.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/TaskQueue.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsIEventTarget.h" +#include "nsIFileStreams.h" +#include "nsNetCID.h" +#include "nsPIDOMWindow.h" +#include "nsThreadUtils.h" +#include "nsTraceRefcnt.h" +#include "ProfilerHelpers.h" +#include "ReportInternalError.h" +#include "ThreadLocal.h" + +#ifdef DEBUG +# include "IndexedDatabaseManager.h" +#endif + +#define GC_ON_IPC_MESSAGES 0 + +#if defined(DEBUG) || GC_ON_IPC_MESSAGES + +# include "js/GCAPI.h" +# include "nsJSEnvironment.h" + +# define BUILD_GC_ON_IPC_MESSAGES + +#endif // DEBUG || GC_ON_IPC_MESSAGES + +namespace mozilla { + +using ipc::PrincipalInfo; + +namespace dom::indexedDB { + +/******************************************************************************* + * ThreadLocal + ******************************************************************************/ + +ThreadLocal::ThreadLocal(const nsID& aBackgroundChildLoggingId) + : mLoggingInfo(aBackgroundChildLoggingId, 1, -1, 1), + mLoggingIdString(aBackgroundChildLoggingId) { + MOZ_COUNT_CTOR(mozilla::dom::indexedDB::ThreadLocal); +} + +ThreadLocal::~ThreadLocal() { + MOZ_COUNT_DTOR(mozilla::dom::indexedDB::ThreadLocal); +} + +/******************************************************************************* + * Helpers + ******************************************************************************/ + +namespace { + +void MaybeCollectGarbageOnIPCMessage() { +#ifdef BUILD_GC_ON_IPC_MESSAGES + static const bool kCollectGarbageOnIPCMessages = +# if GC_ON_IPC_MESSAGES + true; +# else + false; +# endif // GC_ON_IPC_MESSAGES + + if (!kCollectGarbageOnIPCMessages) { + return; + } + + static bool haveWarnedAboutGC = false; + static bool haveWarnedAboutNonMainThread = false; + + if (!haveWarnedAboutGC) { + haveWarnedAboutGC = true; + NS_WARNING("IndexedDB child actor GC debugging enabled!"); + } + + if (!NS_IsMainThread()) { + if (!haveWarnedAboutNonMainThread) { + haveWarnedAboutNonMainThread = true; + NS_WARNING("Don't know how to GC on a non-main thread yet."); + } + return; + } + + nsJSContext::GarbageCollectNow(JS::GCReason::DOM_IPC); + nsJSContext::CycleCollectNow(CCReason::API); +#endif // BUILD_GC_ON_IPC_MESSAGES +} + +class MOZ_STACK_CLASS AutoSetCurrentTransaction final { + using BackgroundChildImpl = mozilla::ipc::BackgroundChildImpl; + + Maybe<IDBTransaction&> const mTransaction; + Maybe<IDBTransaction&> mPreviousTransaction; + ThreadLocal* mThreadLocal; + + public: + AutoSetCurrentTransaction(const AutoSetCurrentTransaction&) = delete; + AutoSetCurrentTransaction(AutoSetCurrentTransaction&&) = delete; + AutoSetCurrentTransaction& operator=(const AutoSetCurrentTransaction&) = + delete; + AutoSetCurrentTransaction& operator=(AutoSetCurrentTransaction&&) = delete; + + explicit AutoSetCurrentTransaction(Maybe<IDBTransaction&> aTransaction) + : mTransaction(aTransaction), + mPreviousTransaction(), + mThreadLocal(nullptr) { + if (aTransaction) { + BackgroundChildImpl::ThreadLocal* threadLocal = + BackgroundChildImpl::GetThreadLocalForCurrentThread(); + MOZ_ASSERT(threadLocal); + + // Hang onto this for resetting later. + mThreadLocal = threadLocal->mIndexedDBThreadLocal.get(); + MOZ_ASSERT(mThreadLocal); + + // Save the current value. + mPreviousTransaction = mThreadLocal->MaybeCurrentTransactionRef(); + + // Set the new value. + mThreadLocal->SetCurrentTransaction(aTransaction); + } + } + + ~AutoSetCurrentTransaction() { + MOZ_ASSERT_IF(mThreadLocal, mTransaction); + MOZ_ASSERT_IF(mThreadLocal, + ReferenceEquals(mThreadLocal->MaybeCurrentTransactionRef(), + mTransaction)); + + if (mThreadLocal) { + // Reset old value. + mThreadLocal->SetCurrentTransaction(mPreviousTransaction); + } + } +}; + +template <typename T> +void SetResultAndDispatchSuccessEvent( + const NotNull<RefPtr<IDBRequest>>& aRequest, + const SafeRefPtr<IDBTransaction>& aTransaction, T& aPtr, + RefPtr<Event> aEvent = nullptr); + +namespace detail { +void DispatchSuccessEvent(const NotNull<RefPtr<IDBRequest>>& aRequest, + const SafeRefPtr<IDBTransaction>& aTransaction, + const RefPtr<Event>& aEvent); + +template <class T> +std::enable_if_t<std::is_same_v<T, IDBDatabase> || + std::is_same_v<T, IDBCursor> || + std::is_same_v<T, IDBMutableFile>, + nsresult> +GetResult(JSContext* aCx, T* aDOMObject, JS::MutableHandle<JS::Value> aResult) { + if (!aDOMObject) { + aResult.setNull(); + return NS_OK; + } + + const bool ok = GetOrCreateDOMReflector(aCx, aDOMObject, aResult); + if (NS_WARN_IF(!ok)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + return NS_OK; +} + +nsresult GetResult(JSContext* aCx, const JS::Handle<JS::Value>* aValue, + JS::MutableHandle<JS::Value> aResult) { + aResult.set(*aValue); + return NS_OK; +} + +nsresult GetResult(JSContext* aCx, const uint64_t* aValue, + JS::MutableHandle<JS::Value> aResult) { + aResult.set(JS::NumberValue(*aValue)); + return NS_OK; +} + +nsresult GetResult(JSContext* aCx, StructuredCloneReadInfoChild&& aCloneInfo, + JS::MutableHandle<JS::Value> aResult) { + const bool ok = + IDBObjectStore::DeserializeValue(aCx, std::move(aCloneInfo), aResult); + + if (NS_WARN_IF(!ok)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + + return NS_OK; +} + +nsresult GetResult(JSContext* aCx, StructuredCloneReadInfoChild* aCloneInfo, + JS::MutableHandle<JS::Value> aResult) { + return GetResult(aCx, std::move(*aCloneInfo), aResult); +} + +nsresult GetResult(JSContext* aCx, + nsTArray<StructuredCloneReadInfoChild>* aCloneInfos, + JS::MutableHandle<JS::Value> aResult) { + JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0)); + if (NS_WARN_IF(!array)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + if (!aCloneInfos->IsEmpty()) { + const uint32_t count = aCloneInfos->Length(); + + if (NS_WARN_IF(!JS::SetArrayLength(aCx, array, count))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + for (uint32_t index = 0; index < count; index++) { + auto& cloneInfo = aCloneInfos->ElementAt(index); + + JS::Rooted<JS::Value> value(aCx); + + const nsresult rv = GetResult(aCx, std::move(cloneInfo), &value); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF( + !JS_DefineElement(aCx, array, index, value, JSPROP_ENUMERATE))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + } + } + + aResult.setObject(*array); + return NS_OK; +} + +nsresult GetResult(JSContext* aCx, const Key* aKey, + JS::MutableHandle<JS::Value> aResult) { + const nsresult rv = aKey->ToJSVal(aCx, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +nsresult GetResult(JSContext* aCx, const nsTArray<Key>* aKeys, + JS::MutableHandle<JS::Value> aResult) { + JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0)); + if (NS_WARN_IF(!array)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + if (!aKeys->IsEmpty()) { + const uint32_t count = aKeys->Length(); + + if (NS_WARN_IF(!JS::SetArrayLength(aCx, array, count))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + for (uint32_t index = 0; index < count; index++) { + const Key& key = aKeys->ElementAt(index); + MOZ_ASSERT(!key.IsUnset()); + + JS::Rooted<JS::Value> value(aCx); + + const nsresult rv = GetResult(aCx, &key, &value); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF( + !JS_DefineElement(aCx, array, index, value, JSPROP_ENUMERATE))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + } + } + + aResult.setObject(*array); + return NS_OK; +} +} // namespace detail + +auto DeserializeStructuredCloneFiles( + IDBDatabase* aDatabase, + const nsTArray<SerializedStructuredCloneFile>& aSerializedFiles, + bool aForPreprocess) { + MOZ_ASSERT_IF(aForPreprocess, aSerializedFiles.Length() == 1); + + return TransformIntoNewArray( + aSerializedFiles, + [aForPreprocess, &database = *aDatabase]( + const auto& serializedFile) -> StructuredCloneFileChild { + MOZ_ASSERT_IF( + aForPreprocess, + serializedFile.type() == StructuredCloneFileBase::eStructuredClone); + + const BlobOrMutableFile& blobOrMutableFile = serializedFile.file(); + + switch (serializedFile.type()) { + case StructuredCloneFileBase::eBlob: { + MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::TIPCBlob); + + const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob(); + + const RefPtr<BlobImpl> blobImpl = + IPCBlobUtils::Deserialize(ipcBlob); + MOZ_ASSERT(blobImpl); + + RefPtr<Blob> blob = + Blob::Create(database.GetOwnerGlobal(), blobImpl); + MOZ_ASSERT(blob); + + return {StructuredCloneFileBase::eBlob, std::move(blob)}; + } + + case StructuredCloneFileBase::eMutableFile: { + MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t || + blobOrMutableFile.type() == + BlobOrMutableFile::TPBackgroundMutableFileChild); + + switch (blobOrMutableFile.type()) { + case BlobOrMutableFile::Tnull_t: + return StructuredCloneFileChild{ + StructuredCloneFileBase::eMutableFile}; + + case BlobOrMutableFile::TPBackgroundMutableFileChild: { + auto* const actor = static_cast<BackgroundMutableFileChild*>( + blobOrMutableFile.get_PBackgroundMutableFileChild()); + MOZ_ASSERT(actor); + + actor->EnsureDOMObject(); + + auto* const mutableFile = + static_cast<IDBMutableFile*>(actor->GetDOMObject()); + MOZ_ASSERT(mutableFile); + + auto file = StructuredCloneFileChild{mutableFile}; + + actor->ReleaseDOMObject(); + + return file; + } + + default: + MOZ_CRASH("Should never get here!"); + } + } + + case StructuredCloneFileBase::eStructuredClone: { + if (aForPreprocess) { + MOZ_ASSERT(blobOrMutableFile.type() == + BlobOrMutableFile::TIPCBlob); + + const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob(); + + const RefPtr<BlobImpl> blobImpl = + IPCBlobUtils::Deserialize(ipcBlob); + MOZ_ASSERT(blobImpl); + + RefPtr<Blob> blob = + Blob::Create(database.GetOwnerGlobal(), blobImpl); + MOZ_ASSERT(blob); + + return {StructuredCloneFileBase::eStructuredClone, + std::move(blob)}; + } + MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t); + + return StructuredCloneFileChild{ + StructuredCloneFileBase::eStructuredClone}; + } + + case StructuredCloneFileBase::eWasmBytecode: + case StructuredCloneFileBase::eWasmCompiled: { + MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t); + + return StructuredCloneFileChild{serializedFile.type()}; + + // Don't set mBlob, support for storing WebAssembly.Modules has been + // removed in bug 1469395. Support for de-serialization of + // WebAssembly.Modules has been removed in bug 1561876. Full removal + // is tracked in bug 1487479. + } + + default: + MOZ_CRASH("Should never get here!"); + } + }); +} + +JSStructuredCloneData PreprocessingNotSupported() { + MOZ_CRASH("Preprocessing not (yet) supported!"); +} + +template <typename PreprocessInfoAccessor> +StructuredCloneReadInfoChild DeserializeStructuredCloneReadInfo( + SerializedStructuredCloneReadInfo&& aSerialized, + IDBDatabase* const aDatabase, + PreprocessInfoAccessor preprocessInfoAccessor) { + // XXX Make this a class invariant of SerializedStructuredCloneReadInfo. + MOZ_ASSERT_IF(aSerialized.hasPreprocessInfo(), + 0 == aSerialized.data().data.Size()); + return {aSerialized.hasPreprocessInfo() ? preprocessInfoAccessor() + : std::move(aSerialized.data().data), + DeserializeStructuredCloneFiles(aDatabase, aSerialized.files(), + /* aForPreprocess */ false), + aDatabase}; +} + +// TODO: Remove duplication between DispatchErrorEvent and DispatchSucessEvent. + +void DispatchErrorEvent( + MovingNotNull<RefPtr<IDBRequest>> aRequest, nsresult aErrorCode, + const SafeRefPtr<IDBTransaction>& aTransaction = nullptr, + RefPtr<Event> aEvent = nullptr) { + const RefPtr<IDBRequest> request = std::move(aRequest); + + request->AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aErrorCode)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_INDEXEDDB); + + AUTO_PROFILER_LABEL("IndexedDB:DispatchErrorEvent", DOM); + + request->SetError(aErrorCode); + + if (!aEvent) { + // Make an error event and fire it at the target. + aEvent = CreateGenericEvent(request, nsDependentString(kErrorEventType), + eDoesBubble, eCancelable); + } + MOZ_ASSERT(aEvent); + + // XXX This is redundant if we are called from + // DispatchSuccessEvent. + Maybe<AutoSetCurrentTransaction> asct; + if (aTransaction) { + asct.emplace(SomeRef(*aTransaction)); + } + + if (aTransaction && aTransaction->IsInactive()) { + aTransaction->TransitionToActive(); + } + + if (aTransaction) { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "Firing %s event with error 0x%x", "%s (0x%" PRIx32 ")", + aTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(), + IDB_LOG_STRINGIFY(aEvent, kErrorEventType), + static_cast<uint32_t>(aErrorCode)); + } else { + IDB_LOG_MARK_CHILD_REQUEST("Firing %s event with error 0x%x", + "%s (0x%" PRIx32 ")", + request->LoggingSerialNumber(), + IDB_LOG_STRINGIFY(aEvent, kErrorEventType), + static_cast<uint32_t>(aErrorCode)); + } + + IgnoredErrorResult rv; + const bool doDefault = + request->DispatchEvent(*aEvent, CallerType::System, rv); + if (NS_WARN_IF(rv.Failed())) { + return; + } + + MOZ_ASSERT(!aTransaction || aTransaction->IsActive() || + aTransaction->IsAborted() || + aTransaction->WasExplicitlyCommitted()); + + if (aTransaction && aTransaction->IsActive()) { + aTransaction->TransitionToInactive(); + + // Do not abort the transaction here if this request is failed due to the + // abortion of its transaction to ensure that the correct error cause of + // the abort event be set in IDBTransaction::FireCompleteOrAbortEvents() + // later. + if (aErrorCode != NS_ERROR_DOM_INDEXEDDB_ABORT_ERR) { + WidgetEvent* const internalEvent = aEvent->WidgetEventPtr(); + MOZ_ASSERT(internalEvent); + + if (internalEvent->mFlags.mExceptionWasRaised) { + aTransaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); + } else if (doDefault) { + aTransaction->Abort(request); + } + } + } +} + +template <typename T> +void SetResultAndDispatchSuccessEvent( + const NotNull<RefPtr<IDBRequest>>& aRequest, + const SafeRefPtr<IDBTransaction>& aTransaction, T& aPtr, + RefPtr<Event> aEvent) { + const auto autoTransaction = + AutoSetCurrentTransaction{aTransaction.maybeDeref()}; + + AUTO_PROFILER_LABEL("IndexedDB:SetResultAndDispatchSuccessEvent", DOM); + + aRequest->AssertIsOnOwningThread(); + + if (aTransaction && aTransaction->IsAborted()) { + DispatchErrorEvent(aRequest, aTransaction->AbortCode(), aTransaction); + return; + } + + if (!aEvent) { + aEvent = + CreateGenericEvent(aRequest.get(), nsDependentString(kSuccessEventType), + eDoesNotBubble, eNotCancelable); + } + MOZ_ASSERT(aEvent); + + aRequest->SetResult( + [&aPtr](JSContext* aCx, JS::MutableHandle<JS::Value> aResult) { + MOZ_ASSERT(aCx); + return detail::GetResult(aCx, &aPtr, aResult); + }); + + detail::DispatchSuccessEvent(aRequest, aTransaction, aEvent); +} + +namespace detail { +void DispatchSuccessEvent(const NotNull<RefPtr<IDBRequest>>& aRequest, + const SafeRefPtr<IDBTransaction>& aTransaction, + const RefPtr<Event>& aEvent) { + if (aTransaction && aTransaction->IsInactive()) { + aTransaction->TransitionToActive(); + } + + if (aTransaction) { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "Firing %s event", "%s", aTransaction->LoggingSerialNumber(), + aRequest->LoggingSerialNumber(), + IDB_LOG_STRINGIFY(aEvent, kSuccessEventType)); + } else { + IDB_LOG_MARK_CHILD_REQUEST("Firing %s event", "%s", + aRequest->LoggingSerialNumber(), + IDB_LOG_STRINGIFY(aEvent, kSuccessEventType)); + } + + MOZ_ASSERT_IF(aTransaction && !aTransaction->WasExplicitlyCommitted(), + aTransaction->IsActive() && !aTransaction->IsAborted()); + + IgnoredErrorResult rv; + aRequest->DispatchEvent(*aEvent, rv); + if (NS_WARN_IF(rv.Failed())) { + return; + } + + WidgetEvent* const internalEvent = aEvent->WidgetEventPtr(); + MOZ_ASSERT(internalEvent); + + if (aTransaction && aTransaction->IsActive()) { + aTransaction->TransitionToInactive(); + + if (internalEvent->mFlags.mExceptionWasRaised) { + aTransaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); + } else { + // To handle upgrade transaction. + aTransaction->CommitIfNotStarted(); + } + } +} +} // namespace detail + +PRFileDesc* GetFileDescriptorFromStream(nsIInputStream* aStream) { + MOZ_ASSERT(aStream); + + const nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(aStream); + if (NS_WARN_IF(!fileMetadata)) { + return nullptr; + } + + PRFileDesc* fileDesc; + const nsresult rv = fileMetadata->GetFileDescriptor(&fileDesc); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + MOZ_ASSERT(fileDesc); + + return fileDesc; +} + +class MOZ_STACK_CLASS AutoSetCurrentFileHandle final { + using BackgroundChildImpl = mozilla::ipc::BackgroundChildImpl; + + IDBFileHandle* const mFileHandle; + IDBFileHandle* mPreviousFileHandle; + IDBFileHandle** mThreadLocalSlot; + + public: + explicit AutoSetCurrentFileHandle(IDBFileHandle* aFileHandle) + : mFileHandle(aFileHandle), + mPreviousFileHandle(nullptr), + mThreadLocalSlot(nullptr) { + if (aFileHandle) { + BackgroundChildImpl::ThreadLocal* threadLocal = + BackgroundChildImpl::GetThreadLocalForCurrentThread(); + MOZ_ASSERT(threadLocal); + + // Hang onto this location for resetting later. + mThreadLocalSlot = &threadLocal->mCurrentFileHandle; + + // Save the current value. + mPreviousFileHandle = *mThreadLocalSlot; + + // Set the new value. + *mThreadLocalSlot = aFileHandle; + } + } + + ~AutoSetCurrentFileHandle() { + MOZ_ASSERT_IF(mThreadLocalSlot, mFileHandle); + MOZ_ASSERT_IF(mThreadLocalSlot, *mThreadLocalSlot == mFileHandle); + + if (mThreadLocalSlot) { + // Reset old value. + *mThreadLocalSlot = mPreviousFileHandle; + } + } + + IDBFileHandle* FileHandle() const { return mFileHandle; } +}; + +template <typename T> +void SetFileHandleResultAndDispatchSuccessEvent( + const RefPtr<IDBFileRequest>& aFileRequest, + const RefPtr<IDBFileHandle>& aFileHandle, T* aPtr); + +namespace detail { +nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& aFileRequest, + JSContext* aCx, const nsCString* aString, + JS::MutableHandle<JS::Value> aResult) { + const nsCString& data = *aString; + + nsresult rv; + + if (!aFileRequest->HasEncoding()) { + JS::Rooted<JSObject*> arrayBuffer(aCx); + rv = nsContentUtils::CreateArrayBuffer(aCx, data, arrayBuffer.address()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + aResult.setObject(*arrayBuffer); + return NS_OK; + } + + // Try the API argument. + const Encoding* encoding = Encoding::ForLabel(aFileRequest->GetEncoding()); + if (!encoding) { + // API argument failed. Since we are dealing with a file system file, + // we don't have a meaningful type attribute for the blob available, + // so proceeding to the next step, which is defaulting to UTF-8. + encoding = UTF_8_ENCODING; + } + + nsString tmpString; + std::tie(rv, std::ignore) = encoding->Decode(data, tmpString); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + if (NS_WARN_IF(!xpc::StringToJsval(aCx, tmpString, aResult))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + return NS_OK; +} + +nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& /*aFileRequest*/, + JSContext* aCx, + const FileRequestMetadata* aMetadata, + JS::MutableHandle<JS::Value> aResult) { + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!obj)) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + const Maybe<uint64_t>& size = aMetadata->size(); + if (size.isSome()) { + JS::Rooted<JS::Value> number(aCx, JS_NumberValue(size.value())); + + if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "size", number, 0))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + } + + const Maybe<int64_t>& lastModified = aMetadata->lastModified(); + if (lastModified.isSome()) { + JS::Rooted<JSObject*> date( + aCx, JS::NewDateObject(aCx, JS::TimeClip(lastModified.value()))); + if (NS_WARN_IF(!date)) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "lastModified", date, 0))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + } + + aResult.setObject(*obj); + return NS_OK; +} + +nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& /*aFileRequest*/, + JSContext* aCx, + const JS::Handle<JS::Value>* aValue, + JS::MutableHandle<JS::Value> aResult) { + aResult.set(*aValue); + return NS_OK; +} +} // namespace detail + +void DispatchFileHandleErrorEvent(IDBFileRequest* aFileRequest, + nsresult aErrorCode, + IDBFileHandle* aFileHandle) { + MOZ_ASSERT(aFileRequest); + aFileRequest->AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aErrorCode)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_FILEHANDLE); + MOZ_ASSERT(aFileHandle); + + const RefPtr<IDBFileRequest> fileRequest = aFileRequest; + const RefPtr<IDBFileHandle> fileHandle = aFileHandle; + + AutoSetCurrentFileHandle ascfh(aFileHandle); + + fileRequest->FireError(aErrorCode); + + MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted()); +} + +template <typename T> +void SetFileHandleResultAndDispatchSuccessEvent( + const RefPtr<IDBFileRequest>& aFileRequest, + const RefPtr<IDBFileHandle>& aFileHandle, T* aPtr) { + MOZ_ASSERT(aFileRequest); + MOZ_ASSERT(aFileHandle); + MOZ_ASSERT(aPtr); + + auto autoFileHandle = AutoSetCurrentFileHandle{aFileHandle}; + + aFileRequest->AssertIsOnOwningThread(); + + if (aFileHandle->IsAborted()) { + aFileRequest->FireError(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR); + return; + } + + MOZ_ASSERT(aFileHandle->IsOpen()); + + aFileRequest->SetResult( + [aFileRequest, aPtr](JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + return detail::GetFileHandleResult(aFileRequest, aCx, aPtr, aResult); + }); + + MOZ_ASSERT(aFileHandle->IsOpen() || aFileHandle->IsAborted()); +} + +auto GetKeyOperator(const IDBCursorDirection aDirection) { + switch (aDirection) { + case IDBCursorDirection::Next: + case IDBCursorDirection::Nextunique: + return &Key::operator>=; + case IDBCursorDirection::Prev: + case IDBCursorDirection::Prevunique: + return &Key::operator<=; + default: + MOZ_CRASH("Should never get here."); + } +} + +// Does not need to be threadsafe since this only runs on one thread, but +// inheriting from CancelableRunnable is easy. +template <typename T> +class DelayedActionRunnable final : public CancelableRunnable { + using ActionFunc = void (T::*)(); + + SafeRefPtr<T> mActor; + RefPtr<IDBRequest> mRequest; + ActionFunc mActionFunc; + + public: + explicit DelayedActionRunnable(SafeRefPtr<T> aActor, ActionFunc aActionFunc) + : CancelableRunnable("indexedDB::DelayedActionRunnable"), + mActor(std::move(aActor)), + mRequest(mActor->GetRequest()), + mActionFunc(aActionFunc) { + MOZ_ASSERT(mActor); + mActor->AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + MOZ_ASSERT(mActionFunc); + } + + private: + ~DelayedActionRunnable() = default; + + NS_DECL_NSIRUNNABLE + nsresult Cancel() override; +}; + +} // namespace + +/******************************************************************************* + * Actor class declarations + ******************************************************************************/ + +// DiscardableRunnable is used to make workers happy. +class BackgroundRequestChild::PreprocessHelper final + : public DiscardableRunnable, + public nsIInputStreamCallback, + public nsIFileMetadataCallback { + enum class State { + // Just created on the owning thread, dispatched to the thread pool. Next + // step is either Finishing if stream was ready to be read or + // WaitingForStreamReady if the stream is not ready. + Initial, + + // Waiting for stream to be ready on a thread pool thread. Next state is + // Finishing. + WaitingForStreamReady, + + // Waiting to finish/finishing on the owning thread. Next step is Completed. + Finishing, + + // All done. + Completed + }; + + const nsCOMPtr<nsIEventTarget> mOwningEventTarget; + RefPtr<TaskQueue> mTaskQueue; + nsCOMPtr<nsIInputStream> mStream; + UniquePtr<JSStructuredCloneData> mCloneData; + BackgroundRequestChild* mActor; + const uint32_t mCloneDataIndex; + nsresult mResultCode; + State mState; + + public: + PreprocessHelper(uint32_t aCloneDataIndex, BackgroundRequestChild* aActor) + : DiscardableRunnable( + "indexedDB::BackgroundRequestChild::PreprocessHelper"), + mOwningEventTarget(aActor->GetActorEventTarget()), + mActor(aActor), + mCloneDataIndex(aCloneDataIndex), + mResultCode(NS_OK), + mState(State::Initial) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + aActor->AssertIsOnOwningThread(); + } + + bool IsOnOwningThread() const { + MOZ_ASSERT(mOwningEventTarget); + + bool current; + return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) && + current; + } + + void AssertIsOnOwningThread() const { MOZ_ASSERT(IsOnOwningThread()); } + + void ClearActor() { + AssertIsOnOwningThread(); + + mActor = nullptr; + } + + nsresult Init(const StructuredCloneFileChild& aFile); + + nsresult Dispatch(); + + private: + ~PreprocessHelper() { + MOZ_ASSERT(mState == State::Initial || mState == State::Completed); + + if (mTaskQueue) { + mTaskQueue->BeginShutdown(); + } + } + + nsresult Start(); + + nsresult ProcessStream(); + + void Finish(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIFILEMETADATACALLBACK +}; + +/******************************************************************************* + * BackgroundRequestChildBase + ******************************************************************************/ + +BackgroundRequestChildBase::BackgroundRequestChildBase( + MovingNotNull<RefPtr<IDBRequest>> aRequest) + : mRequest(std::move(aRequest)) { + mRequest->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChildBase); +} + +BackgroundRequestChildBase::~BackgroundRequestChildBase() { + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChildBase); +} + +#ifdef DEBUG + +void BackgroundRequestChildBase::AssertIsOnOwningThread() const { + mRequest->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +/******************************************************************************* + * BackgroundFactoryChild + ******************************************************************************/ + +BackgroundFactoryChild::BackgroundFactoryChild(IDBFactory& aFactory) + : mFactory(&aFactory) { + AssertIsOnOwningThread(); + mFactory->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryChild); +} + +BackgroundFactoryChild::~BackgroundFactoryChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryChild); +} + +void BackgroundFactoryChild::SendDeleteMeInternal() { + AssertIsOnOwningThread(); + + if (mFactory) { + mFactory->ClearBackgroundActor(); + mFactory = nullptr; + + MOZ_ALWAYS_TRUE(PBackgroundIDBFactoryChild::SendDeleteMe()); + } +} + +void BackgroundFactoryChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + if (mFactory) { + mFactory->ClearBackgroundActor(); +#ifdef DEBUG + mFactory = nullptr; +#endif + } +} + +PBackgroundIDBFactoryRequestChild* +BackgroundFactoryChild::AllocPBackgroundIDBFactoryRequestChild( + const FactoryRequestParams& aParams) { + MOZ_CRASH( + "PBackgroundIDBFactoryRequestChild actors should be manually " + "constructed!"); +} + +bool BackgroundFactoryChild::DeallocPBackgroundIDBFactoryRequestChild( + PBackgroundIDBFactoryRequestChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundFactoryRequestChild*>(aActor); + return true; +} + +PBackgroundIDBDatabaseChild* +BackgroundFactoryChild::AllocPBackgroundIDBDatabaseChild( + const DatabaseSpec& aSpec, + PBackgroundIDBFactoryRequestChild* aRequest) const { + AssertIsOnOwningThread(); + + auto* const request = static_cast<BackgroundFactoryRequestChild*>(aRequest); + MOZ_ASSERT(request); + + return new BackgroundDatabaseChild(aSpec, request); +} + +bool BackgroundFactoryChild::DeallocPBackgroundIDBDatabaseChild( + PBackgroundIDBDatabaseChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundDatabaseChild*>(aActor); + return true; +} + +mozilla::ipc::IPCResult +BackgroundFactoryChild::RecvPBackgroundIDBDatabaseConstructor( + PBackgroundIDBDatabaseChild* aActor, const DatabaseSpec& aSpec, + PBackgroundIDBFactoryRequestChild* aRequest) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + + return IPC_OK(); +} + +/******************************************************************************* + * BackgroundFactoryRequestChild + ******************************************************************************/ + +BackgroundFactoryRequestChild::BackgroundFactoryRequestChild( + SafeRefPtr<IDBFactory> aFactory, + MovingNotNull<RefPtr<IDBOpenDBRequest>> aOpenRequest, bool aIsDeleteOp, + uint64_t aRequestedVersion) + : BackgroundRequestChildBase(std::move(aOpenRequest)), + mFactory(std::move(aFactory)), + mDatabaseActor(nullptr), + mRequestedVersion(aRequestedVersion), + mIsDeleteOp(aIsDeleteOp) { + // Can't assert owning thread here because IPDL has not yet set our manager! + MOZ_ASSERT(mFactory); + mFactory->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryRequestChild); +} + +BackgroundFactoryRequestChild::~BackgroundFactoryRequestChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryRequestChild); +} + +NotNull<IDBOpenDBRequest*> BackgroundFactoryRequestChild::GetOpenDBRequest() + const { + AssertIsOnOwningThread(); + + // XXX NotNull might provide something to encapsulate this + return WrapNotNullUnchecked( + static_cast<IDBOpenDBRequest*>(mRequest.get().get())); +} + +void BackgroundFactoryRequestChild::SetDatabaseActor( + BackgroundDatabaseChild* aActor) { + AssertIsOnOwningThread(); + MOZ_ASSERT(!aActor || !mDatabaseActor); + + mDatabaseActor = aActor; +} + +void BackgroundFactoryRequestChild::HandleResponse(nsresult aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB); + + mRequest->Reset(); + + DispatchErrorEvent(mRequest, aResponse); + + if (mDatabaseActor) { + mDatabaseActor->ReleaseDOMObject(); + MOZ_ASSERT(!mDatabaseActor); + } +} + +void BackgroundFactoryRequestChild::HandleResponse( + const OpenDatabaseRequestResponse& aResponse) { + AssertIsOnOwningThread(); + + mRequest->Reset(); + + auto* databaseActor = + static_cast<BackgroundDatabaseChild*>(aResponse.databaseChild()); + MOZ_ASSERT(databaseActor); + + IDBDatabase* const database = [this, databaseActor]() -> IDBDatabase* { + IDBDatabase* database = databaseActor->GetDOMObject(); + if (!database) { + Unused << this; + + if (NS_WARN_IF(!databaseActor->EnsureDOMObject())) { + return nullptr; + } + MOZ_ASSERT(mDatabaseActor); + + database = databaseActor->GetDOMObject(); + MOZ_ASSERT(database); + + MOZ_ASSERT(!database->IsClosed()); + } + + return database; + }(); + + if (!database || database->IsClosed()) { + // If the database was closed already, which is only possible if we fired an + // "upgradeneeded" event, then we shouldn't fire a "success" event here. + // Instead we fire an error event with AbortErr. + DispatchErrorEvent(mRequest, NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); + } else { + SetResultAndDispatchSuccessEvent(mRequest, nullptr, *database); + } + + if (database) { + MOZ_ASSERT(mDatabaseActor == databaseActor); + + databaseActor->ReleaseDOMObject(); + } else { + databaseActor->SendDeleteMeInternal(); + } + MOZ_ASSERT(!mDatabaseActor); +} + +void BackgroundFactoryRequestChild::HandleResponse( + const DeleteDatabaseRequestResponse& aResponse) { + AssertIsOnOwningThread(); + + RefPtr<Event> successEvent = IDBVersionChangeEvent::Create( + mRequest.get(), nsDependentString(kSuccessEventType), + aResponse.previousVersion()); + MOZ_ASSERT(successEvent); + + SetResultAndDispatchSuccessEvent(mRequest, nullptr, JS::UndefinedHandleValue, + std::move(successEvent)); + + MOZ_ASSERT(!mDatabaseActor); +} + +void BackgroundFactoryRequestChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + if (aWhy != Deletion) { + GetOpenDBRequest()->NoteComplete(); + } +} + +mozilla::ipc::IPCResult BackgroundFactoryRequestChild::Recv__delete__( + const FactoryRequestResponse& aResponse) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + switch (aResponse.type()) { + case FactoryRequestResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case FactoryRequestResponse::TOpenDatabaseRequestResponse: + HandleResponse(aResponse.get_OpenDatabaseRequestResponse()); + break; + + case FactoryRequestResponse::TDeleteDatabaseRequestResponse: + HandleResponse(aResponse.get_DeleteDatabaseRequestResponse()); + break; + + default: + return IPC_FAIL(this, "Unknown response type!"); + } + + auto request = GetOpenDBRequest(); + request->NoteComplete(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundFactoryRequestChild::RecvBlocked( + const uint64_t aCurrentVersion) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + const nsDependentString type(kBlockedEventType); + + RefPtr<Event> blockedEvent; + if (mIsDeleteOp) { + blockedEvent = + IDBVersionChangeEvent::Create(mRequest.get(), type, aCurrentVersion); + MOZ_ASSERT(blockedEvent); + } else { + blockedEvent = IDBVersionChangeEvent::Create( + mRequest.get(), type, aCurrentVersion, mRequestedVersion); + MOZ_ASSERT(blockedEvent); + } + + RefPtr<IDBRequest> kungFuDeathGrip = mRequest; + + IDB_LOG_MARK_CHILD_REQUEST("Firing \"blocked\" event", "\"blocked\"", + kungFuDeathGrip->LoggingSerialNumber()); + + IgnoredErrorResult rv; + kungFuDeathGrip->DispatchEvent(*blockedEvent, rv); + if (rv.Failed()) { + NS_WARNING("Failed to dispatch event!"); + } + + return IPC_OK(); +} + +/******************************************************************************* + * BackgroundDatabaseChild + ******************************************************************************/ + +BackgroundDatabaseChild::BackgroundDatabaseChild( + const DatabaseSpec& aSpec, BackgroundFactoryRequestChild* aOpenRequestActor) + : mSpec(MakeUnique<DatabaseSpec>(aSpec)), + mOpenRequestActor(aOpenRequestActor), + mDatabase(nullptr) { + // Can't assert owning thread here because IPDL has not yet set our manager! + MOZ_ASSERT(aOpenRequestActor); + + MOZ_COUNT_CTOR(indexedDB::BackgroundDatabaseChild); +} + +BackgroundDatabaseChild::~BackgroundDatabaseChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundDatabaseChild); +} + +#ifdef DEBUG + +void BackgroundDatabaseChild::AssertIsOnOwningThread() const { + static_cast<BackgroundFactoryChild*>(Manager())->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void BackgroundDatabaseChild::SendDeleteMeInternal() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mTemporaryStrongDatabase); + MOZ_ASSERT(!mOpenRequestActor); + + if (mDatabase) { + mDatabase->ClearBackgroundActor(); + mDatabase = nullptr; + + MOZ_ALWAYS_TRUE(PBackgroundIDBDatabaseChild::SendDeleteMe()); + } +} + +bool BackgroundDatabaseChild::EnsureDOMObject() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mOpenRequestActor); + + if (mTemporaryStrongDatabase) { + MOZ_ASSERT(!mSpec); + MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase); + return true; + } + + MOZ_ASSERT(mSpec); + + const auto request = mOpenRequestActor->GetOpenDBRequest(); + + auto& factory = + static_cast<BackgroundFactoryChild*>(Manager())->GetDOMObject(); + + if (!factory.GetParentObject()) { + // Already disconnected from global. + + // We need to clear mOpenRequestActor here, since that would otherwise be + // done by ReleaseDOMObject, which cannot be called if EnsureDOMObject + // failed. + mOpenRequestActor = nullptr; + + return false; + } + + // TODO: This AcquireStrongRefFromRawPtr looks suspicious. This should be + // changed or at least well explained, see also comment on + // BackgroundFactoryChild. + mTemporaryStrongDatabase = IDBDatabase::Create( + request, SafeRefPtr{&factory, AcquireStrongRefFromRawPtr{}}, this, + std::move(mSpec)); + + MOZ_ASSERT(mTemporaryStrongDatabase); + mTemporaryStrongDatabase->AssertIsOnOwningThread(); + + mDatabase = mTemporaryStrongDatabase; + + mOpenRequestActor->SetDatabaseActor(this); + + return true; +} + +void BackgroundDatabaseChild::ReleaseDOMObject() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTemporaryStrongDatabase); + mTemporaryStrongDatabase->AssertIsOnOwningThread(); + MOZ_ASSERT(mOpenRequestActor); + MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase); + + mOpenRequestActor->SetDatabaseActor(nullptr); + + mOpenRequestActor = nullptr; + + // This may be the final reference to the IDBDatabase object so we may end up + // calling SendDeleteMeInternal() here. Make sure everything is cleaned up + // properly before proceeding. + mTemporaryStrongDatabase = nullptr; + + // XXX Why isn't mDatabase set to nullptr here? +} + +void BackgroundDatabaseChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + if (mDatabase) { + mDatabase->ClearBackgroundActor(); +#ifdef DEBUG + mDatabase = nullptr; +#endif + } +} + +PBackgroundIDBDatabaseFileChild* +BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseFileChild( + const IPCBlob& aIPCBlob) { + MOZ_CRASH("PBackgroundIDBFileChild actors should be manually constructed!"); +} + +bool BackgroundDatabaseChild::DeallocPBackgroundIDBDatabaseFileChild( + PBackgroundIDBDatabaseFileChild* aActor) const { + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +PBackgroundIDBDatabaseRequestChild* +BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseRequestChild( + const DatabaseRequestParams& aParams) { + MOZ_CRASH( + "PBackgroundIDBDatabaseRequestChild actors should be manually " + "constructed!"); +} + +bool BackgroundDatabaseChild::DeallocPBackgroundIDBDatabaseRequestChild( + PBackgroundIDBDatabaseRequestChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundDatabaseRequestChild*>(aActor); + return true; +} + +already_AddRefed<PBackgroundIDBVersionChangeTransactionChild> +BackgroundDatabaseChild::AllocPBackgroundIDBVersionChangeTransactionChild( + const uint64_t aCurrentVersion, const uint64_t aRequestedVersion, + const int64_t aNextObjectStoreId, const int64_t aNextIndexId) { + AssertIsOnOwningThread(); + + return RefPtr{new BackgroundVersionChangeTransactionChild( + mOpenRequestActor->GetOpenDBRequest())} + .forget(); +} + +mozilla::ipc::IPCResult +BackgroundDatabaseChild::RecvPBackgroundIDBVersionChangeTransactionConstructor( + PBackgroundIDBVersionChangeTransactionChild* aActor, + const uint64_t& aCurrentVersion, const uint64_t& aRequestedVersion, + const int64_t& aNextObjectStoreId, const int64_t& aNextIndexId) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(mOpenRequestActor); + + MaybeCollectGarbageOnIPCMessage(); + + auto* const actor = + static_cast<BackgroundVersionChangeTransactionChild*>(aActor); + + if (!EnsureDOMObject()) { + NS_WARNING("Factory is already disconnected from global"); + + actor->SendDeleteMeInternal(true); + + // XXX This is a hack to ensure that transaction/request serial numbers stay + // in sync between parent and child. Actually, it might be better to create + // an IDBTransaction in the child and abort that. + Unused + << mozilla::ipc::BackgroundChildImpl::GetThreadLocalForCurrentThread() + ->mIndexedDBThreadLocal->NextTransactionSN( + IDBTransaction::Mode::VersionChange); + Unused << IDBRequest::NextSerialNumber(); + + // No reason to IPC_FAIL here. + return IPC_OK(); + } + + MOZ_ASSERT(!mDatabase->IsInvalidated()); + + // XXX NotNull might encapsulate this + const auto request = + WrapNotNullUnchecked(RefPtr{mOpenRequestActor->GetOpenDBRequest().get()}); + + SafeRefPtr<IDBTransaction> transaction = IDBTransaction::CreateVersionChange( + mDatabase, actor, request, aNextObjectStoreId, aNextIndexId); + MOZ_ASSERT(transaction); + + transaction->AssertIsOnOwningThread(); + + actor->SetDOMTransaction(transaction.clonePtr()); + + const auto database = WrapNotNull(mDatabase); + + database->EnterSetVersionTransaction(aRequestedVersion); + + request->SetTransaction(transaction.clonePtr()); + + RefPtr<Event> upgradeNeededEvent = IDBVersionChangeEvent::Create( + request.get(), nsDependentString(kUpgradeNeededEventType), + aCurrentVersion, aRequestedVersion); + MOZ_ASSERT(upgradeNeededEvent); + + SetResultAndDispatchSuccessEvent( + WrapNotNullUnchecked<RefPtr<IDBRequest>>(request.get()), transaction, + *database, std::move(upgradeNeededEvent)); + + return IPC_OK(); +} + +PBackgroundMutableFileChild* +BackgroundDatabaseChild::AllocPBackgroundMutableFileChild( + const nsString& aName, const nsString& aType) const { + AssertIsOnOwningThread(); + + return new BackgroundMutableFileChild(aName, aType); +} + +bool BackgroundDatabaseChild::DeallocPBackgroundMutableFileChild( + PBackgroundMutableFileChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundMutableFileChild*>(aActor); + return true; +} + +mozilla::ipc::IPCResult BackgroundDatabaseChild::RecvVersionChange( + const uint64_t aOldVersion, const Maybe<uint64_t> aNewVersion) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + if (!mDatabase || mDatabase->IsClosed()) { + return IPC_OK(); + } + + RefPtr<IDBDatabase> kungFuDeathGrip = mDatabase; + + // Handle bfcache'd windows. + if (nsPIDOMWindowInner* owner = kungFuDeathGrip->GetOwner()) { + // The database must be closed if the window is already frozen. + bool shouldAbortAndClose = owner->IsFrozen(); + + // Anything in the bfcache has to be evicted and then we have to close the + // database also. + if (owner->RemoveFromBFCacheSync()) { + shouldAbortAndClose = true; + } + + if (shouldAbortAndClose) { + // Invalidate() doesn't close the database in the parent, so we have + // to call Close() and AbortTransactions() manually. + kungFuDeathGrip->AbortTransactions(/* aShouldWarn */ false); + kungFuDeathGrip->Close(); + return IPC_OK(); + } + } + + // Otherwise fire a versionchange event. + const nsDependentString type(kVersionChangeEventType); + + RefPtr<Event> versionChangeEvent; + + if (aNewVersion.isNothing()) { + versionChangeEvent = + IDBVersionChangeEvent::Create(kungFuDeathGrip, type, aOldVersion); + MOZ_ASSERT(versionChangeEvent); + } else { + versionChangeEvent = IDBVersionChangeEvent::Create( + kungFuDeathGrip, type, aOldVersion, aNewVersion.value()); + MOZ_ASSERT(versionChangeEvent); + } + + IDB_LOG_MARK("Child : Firing \"versionchange\" event", + "C: IDBDatabase \"versionchange\" event", IDB_LOG_ID_STRING()); + + IgnoredErrorResult rv; + kungFuDeathGrip->DispatchEvent(*versionChangeEvent, rv); + if (rv.Failed()) { + NS_WARNING("Failed to dispatch event!"); + } + + if (!kungFuDeathGrip->IsClosed()) { + SendBlocked(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundDatabaseChild::RecvInvalidate() { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + if (mDatabase) { + mDatabase->Invalidate(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +BackgroundDatabaseChild::RecvCloseAfterInvalidationComplete() { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + if (mDatabase) { + mDatabase->DispatchTrustedEvent(nsDependentString(kCloseEventType)); + } + + return IPC_OK(); +} + +/******************************************************************************* + * BackgroundDatabaseRequestChild + ******************************************************************************/ + +BackgroundDatabaseRequestChild::BackgroundDatabaseRequestChild( + IDBDatabase* aDatabase, MovingNotNull<RefPtr<IDBRequest>> aRequest) + : BackgroundRequestChildBase(std::move(aRequest)), mDatabase(aDatabase) { + // Can't assert owning thread here because IPDL has not yet set our manager! + MOZ_ASSERT(aDatabase); + aDatabase->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(indexedDB::BackgroundDatabaseRequestChild); +} + +BackgroundDatabaseRequestChild::~BackgroundDatabaseRequestChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundDatabaseRequestChild); +} + +void BackgroundDatabaseRequestChild::HandleResponse(nsresult aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB); + + mRequest->Reset(); + + DispatchErrorEvent(mRequest, aResponse); +} + +void BackgroundDatabaseRequestChild::HandleResponse( + const CreateFileRequestResponse& aResponse) { + AssertIsOnOwningThread(); + + mRequest->Reset(); + + auto mutableFileActor = + static_cast<BackgroundMutableFileChild*>(aResponse.mutableFileChild()); + MOZ_ASSERT(mutableFileActor); + + mutableFileActor->EnsureDOMObject(); + + SetResultAndDispatchSuccessEvent(mRequest, nullptr, + *WrapNotNull(static_cast<IDBMutableFile*>( + mutableFileActor->GetDOMObject()))); + + mutableFileActor->ReleaseDOMObject(); +} + +mozilla::ipc::IPCResult BackgroundDatabaseRequestChild::Recv__delete__( + const DatabaseRequestResponse& aResponse) { + AssertIsOnOwningThread(); + + switch (aResponse.type()) { + case DatabaseRequestResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case DatabaseRequestResponse::TCreateFileRequestResponse: + HandleResponse(aResponse.get_CreateFileRequestResponse()); + break; + + default: + return IPC_FAIL(this, "Unknown response type!"); + } + + return IPC_OK(); +} + +/******************************************************************************* + * BackgroundTransactionBase + ******************************************************************************/ + +BackgroundTransactionBase::BackgroundTransactionBase( + SafeRefPtr<IDBTransaction> aTransaction) + : mTemporaryStrongTransaction(std::move(aTransaction)), + mTransaction(mTemporaryStrongTransaction.unsafeGetRawPtr()) { + MOZ_ASSERT(mTransaction); + mTransaction->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(BackgroundTransactionBase); +} + +#ifdef DEBUG + +void BackgroundTransactionBase::AssertIsOnOwningThread() const { + MOZ_ASSERT(mTransaction); + mTransaction->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void BackgroundTransactionBase::NoteActorDestroyed() { + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(mTemporaryStrongTransaction, mTransaction); + + if (mTransaction) { + mTransaction->ClearBackgroundActor(); + + // Normally this would be DEBUG-only but NoteActorDestroyed is also called + // from SendDeleteMeInternal. In that case we're going to receive an actual + // ActorDestroy call later and we don't want to touch a dead object. + mTemporaryStrongTransaction = nullptr; + mTransaction = nullptr; + } +} + +void BackgroundTransactionBase::SetDOMTransaction( + SafeRefPtr<IDBTransaction> aTransaction) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aTransaction); + aTransaction->AssertIsOnOwningThread(); + MOZ_ASSERT(!mTemporaryStrongTransaction); + MOZ_ASSERT(!mTransaction); + + mTemporaryStrongTransaction = std::move(aTransaction); + mTransaction = mTemporaryStrongTransaction.unsafeGetRawPtr(); +} + +void BackgroundTransactionBase::NoteComplete() { + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(mTransaction, mTemporaryStrongTransaction); + + mTemporaryStrongTransaction = nullptr; +} + +/******************************************************************************* + * BackgroundTransactionChild + ******************************************************************************/ + +BackgroundTransactionChild::BackgroundTransactionChild( + SafeRefPtr<IDBTransaction> aTransaction) + : BackgroundTransactionBase(std::move(aTransaction)) { + MOZ_COUNT_CTOR(indexedDB::BackgroundTransactionChild); +} + +BackgroundTransactionChild::~BackgroundTransactionChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundTransactionChild); +} + +#ifdef DEBUG + +void BackgroundTransactionChild::AssertIsOnOwningThread() const { + static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void BackgroundTransactionChild::SendDeleteMeInternal() { + AssertIsOnOwningThread(); + + if (mTransaction) { + NoteActorDestroyed(); + + MOZ_ALWAYS_TRUE(PBackgroundIDBTransactionChild::SendDeleteMe()); + } +} + +void BackgroundTransactionChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + NoteActorDestroyed(); +} + +mozilla::ipc::IPCResult BackgroundTransactionChild::RecvComplete( + const nsresult aResult) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTransaction); + + MaybeCollectGarbageOnIPCMessage(); + + mTransaction->FireCompleteOrAbortEvents(aResult); + + NoteComplete(); + return IPC_OK(); +} + +PBackgroundIDBRequestChild* +BackgroundTransactionChild::AllocPBackgroundIDBRequestChild( + const RequestParams& aParams) { + MOZ_CRASH( + "PBackgroundIDBRequestChild actors should be manually " + "constructed!"); +} + +bool BackgroundTransactionChild::DeallocPBackgroundIDBRequestChild( + PBackgroundIDBRequestChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundRequestChild*>(aActor); + return true; +} + +PBackgroundIDBCursorChild* +BackgroundTransactionChild::AllocPBackgroundIDBCursorChild( + const OpenCursorParams& aParams) { + AssertIsOnOwningThread(); + + MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!"); +} + +bool BackgroundTransactionChild::DeallocPBackgroundIDBCursorChild( + PBackgroundIDBCursorChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +/******************************************************************************* + * BackgroundVersionChangeTransactionChild + ******************************************************************************/ + +BackgroundVersionChangeTransactionChild:: + BackgroundVersionChangeTransactionChild(IDBOpenDBRequest* aOpenDBRequest) + : mOpenDBRequest(aOpenDBRequest) { + MOZ_ASSERT(aOpenDBRequest); + aOpenDBRequest->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(indexedDB::BackgroundVersionChangeTransactionChild); +} + +BackgroundVersionChangeTransactionChild:: + ~BackgroundVersionChangeTransactionChild() { + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(indexedDB::BackgroundVersionChangeTransactionChild); +} + +#ifdef DEBUG + +void BackgroundVersionChangeTransactionChild::AssertIsOnOwningThread() const { + static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void BackgroundVersionChangeTransactionChild::SendDeleteMeInternal( + bool aFailedConstructor) { + AssertIsOnOwningThread(); + + if (mTransaction || aFailedConstructor) { + NoteActorDestroyed(); + + MOZ_ALWAYS_TRUE( + PBackgroundIDBVersionChangeTransactionChild::SendDeleteMe()); + } +} + +void BackgroundVersionChangeTransactionChild::ActorDestroy( + ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + mOpenDBRequest = nullptr; + + NoteActorDestroyed(); +} + +mozilla::ipc::IPCResult BackgroundVersionChangeTransactionChild::RecvComplete( + const nsresult aResult) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + if (!mTransaction) { + return IPC_OK(); + } + + MOZ_ASSERT(mOpenDBRequest); + + IDBDatabase* database = mTransaction->Database(); + MOZ_ASSERT(database); + + database->ExitSetVersionTransaction(); + + if (NS_FAILED(aResult)) { + database->Close(); + } + + RefPtr<IDBOpenDBRequest> request = mOpenDBRequest; + MOZ_ASSERT(request); + + mTransaction->FireCompleteOrAbortEvents(aResult); + + request->SetTransaction(nullptr); + request = nullptr; + + mOpenDBRequest = nullptr; + + NoteComplete(); + return IPC_OK(); +} + +PBackgroundIDBRequestChild* +BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBRequestChild( + const RequestParams& aParams) { + MOZ_CRASH( + "PBackgroundIDBRequestChild actors should be manually " + "constructed!"); +} + +bool BackgroundVersionChangeTransactionChild::DeallocPBackgroundIDBRequestChild( + PBackgroundIDBRequestChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundRequestChild*>(aActor); + return true; +} + +PBackgroundIDBCursorChild* +BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBCursorChild( + const OpenCursorParams& aParams) { + AssertIsOnOwningThread(); + + MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!"); +} + +bool BackgroundVersionChangeTransactionChild::DeallocPBackgroundIDBCursorChild( + PBackgroundIDBCursorChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +/******************************************************************************* + * BackgroundMutableFileChild + ******************************************************************************/ + +BackgroundMutableFileChild::BackgroundMutableFileChild(const nsAString& aName, + const nsAString& aType) + : mMutableFile(nullptr), mName(aName), mType(aType) { + // Can't assert owning thread here because IPDL has not yet set our manager! + MOZ_COUNT_CTOR(indexedDB::BackgroundMutableFileChild); +} + +BackgroundMutableFileChild::~BackgroundMutableFileChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundMutableFileChild); +} + +#ifdef DEBUG + +void BackgroundMutableFileChild::AssertIsOnOwningThread() const { + static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void BackgroundMutableFileChild::EnsureDOMObject() { + AssertIsOnOwningThread(); + + if (mTemporaryStrongMutableFile) { + return; + } + + auto database = + static_cast<BackgroundDatabaseChild*>(Manager())->GetDOMObject(); + MOZ_ASSERT(database); + + mTemporaryStrongMutableFile = + new IDBMutableFile(database, this, mName, mType); + + MOZ_ASSERT(mTemporaryStrongMutableFile); + mTemporaryStrongMutableFile->AssertIsOnOwningThread(); + + mMutableFile = mTemporaryStrongMutableFile; +} + +void BackgroundMutableFileChild::ReleaseDOMObject() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTemporaryStrongMutableFile); + mTemporaryStrongMutableFile->AssertIsOnOwningThread(); + MOZ_ASSERT(mMutableFile == mTemporaryStrongMutableFile); + + mTemporaryStrongMutableFile = nullptr; +} + +void BackgroundMutableFileChild::SendDeleteMeInternal() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mTemporaryStrongMutableFile); + + if (mMutableFile) { + mMutableFile->ClearBackgroundActor(); + mMutableFile = nullptr; + + MOZ_ALWAYS_TRUE(PBackgroundMutableFileChild::SendDeleteMe()); + } +} + +void BackgroundMutableFileChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + if (mMutableFile) { + mMutableFile->ClearBackgroundActor(); +#ifdef DEBUG + mMutableFile = nullptr; +#endif + } +} + +PBackgroundFileHandleChild* +BackgroundMutableFileChild::AllocPBackgroundFileHandleChild( + const FileMode& aMode) { + MOZ_CRASH( + "PBackgroundFileHandleChild actors should be manually " + "constructed!"); +} + +bool BackgroundMutableFileChild::DeallocPBackgroundFileHandleChild( + PBackgroundFileHandleChild* aActor) const { + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundFileHandleChild*>(aActor); + return true; +} + +/******************************************************************************* + * BackgroundRequestChild + ******************************************************************************/ + +BackgroundRequestChild::BackgroundRequestChild( + MovingNotNull<RefPtr<IDBRequest>> aRequest) + : BackgroundRequestChildBase(std::move(aRequest)), + mTransaction(mRequest->AcquireTransaction()), + mRunningPreprocessHelpers(0), + mCurrentCloneDataIndex(0), + mPreprocessResultCode(NS_OK), + mGetAll(false) { + MOZ_ASSERT(mTransaction); + mTransaction->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChild); +} + +BackgroundRequestChild::~BackgroundRequestChild() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mTransaction); + + MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChild); +} + +void BackgroundRequestChild::MaybeSendContinue() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mRunningPreprocessHelpers > 0); + + if (--mRunningPreprocessHelpers == 0) { + PreprocessResponse response; + + if (NS_SUCCEEDED(mPreprocessResultCode)) { + if (mGetAll) { + response = ObjectStoreGetAllPreprocessResponse(); + } else { + response = ObjectStoreGetPreprocessResponse(); + } + } else { + response = mPreprocessResultCode; + } + + MOZ_ALWAYS_TRUE(SendContinue(response)); + } +} + +void BackgroundRequestChild::OnPreprocessFinished( + uint32_t aCloneDataIndex, UniquePtr<JSStructuredCloneData> aCloneData) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aCloneDataIndex < mCloneInfos.Length()); + MOZ_ASSERT(aCloneData); + + auto& cloneInfo = mCloneInfos[aCloneDataIndex]; + MOZ_ASSERT(cloneInfo.mPreprocessHelper); + MOZ_ASSERT(!cloneInfo.mCloneData); + + cloneInfo.mCloneData = std::move(aCloneData); + + MaybeSendContinue(); + + cloneInfo.mPreprocessHelper = nullptr; +} + +void BackgroundRequestChild::OnPreprocessFailed(uint32_t aCloneDataIndex, + nsresult aErrorCode) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aCloneDataIndex < mCloneInfos.Length()); + MOZ_ASSERT(NS_FAILED(aErrorCode)); + + auto& cloneInfo = mCloneInfos[aCloneDataIndex]; + MOZ_ASSERT(cloneInfo.mPreprocessHelper); + MOZ_ASSERT(!cloneInfo.mCloneData); + + if (NS_SUCCEEDED(mPreprocessResultCode)) { + mPreprocessResultCode = aErrorCode; + } + + MaybeSendContinue(); + + cloneInfo.mPreprocessHelper = nullptr; +} + +UniquePtr<JSStructuredCloneData> BackgroundRequestChild::GetNextCloneData() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mCurrentCloneDataIndex < mCloneInfos.Length()); + MOZ_ASSERT(mCloneInfos[mCurrentCloneDataIndex].mCloneData); + + return std::move(mCloneInfos[mCurrentCloneDataIndex++].mCloneData); +} + +void BackgroundRequestChild::HandleResponse(nsresult aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB); + MOZ_ASSERT(mTransaction); + + DispatchErrorEvent(mRequest, aResponse, mTransaction.clonePtr()); +} + +void BackgroundRequestChild::HandleResponse(const Key& aResponse) { + AssertIsOnOwningThread(); + + SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), aResponse); +} + +void BackgroundRequestChild::HandleResponse(const nsTArray<Key>& aResponse) { + AssertIsOnOwningThread(); + + SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), aResponse); +} + +void BackgroundRequestChild::HandleResponse( + SerializedStructuredCloneReadInfo&& aResponse) { + AssertIsOnOwningThread(); + + auto cloneReadInfo = DeserializeStructuredCloneReadInfo( + std::move(aResponse), mTransaction->Database(), + [this] { return std::move(*GetNextCloneData()); }); + + SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), + cloneReadInfo); +} + +void BackgroundRequestChild::HandleResponse( + nsTArray<SerializedStructuredCloneReadInfo>&& aResponse) { + AssertIsOnOwningThread(); + + nsTArray<StructuredCloneReadInfoChild> cloneReadInfos; + + QM_TRY(OkIf(cloneReadInfos.SetCapacity(aResponse.Length(), fallible)), + QM_VOID, ([&aResponse, this](const auto) { + // Since we are under memory pressure, release aResponse early. + aResponse.Clear(); + + DispatchErrorEvent(mRequest, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, + AcquireTransaction()); + + MOZ_ASSERT(mTransaction->IsAborted()); + })); + + std::transform(std::make_move_iterator(aResponse.begin()), + std::make_move_iterator(aResponse.end()), + MakeBackInserter(cloneReadInfos), + [database = mTransaction->Database(), this]( + SerializedStructuredCloneReadInfo&& serializedCloneInfo) { + return DeserializeStructuredCloneReadInfo( + std::move(serializedCloneInfo), database, + [this] { return std::move(*GetNextCloneData()); }); + }); + + SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), + cloneReadInfos); +} + +void BackgroundRequestChild::HandleResponse(JS::Handle<JS::Value> aResponse) { + AssertIsOnOwningThread(); + + SetResultAndDispatchSuccessEvent( + mRequest, AcquireTransaction(), + const_cast<const JS::Handle<JS::Value>&>(aResponse)); +} + +void BackgroundRequestChild::HandleResponse(const uint64_t aResponse) { + AssertIsOnOwningThread(); + + SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), aResponse); +} + +nsresult BackgroundRequestChild::HandlePreprocess( + const PreprocessInfo& aPreprocessInfo) { + return HandlePreprocessInternal( + AutoTArray<PreprocessInfo, 1>{aPreprocessInfo}); +} + +nsresult BackgroundRequestChild::HandlePreprocess( + const nsTArray<PreprocessInfo>& aPreprocessInfos) { + AssertIsOnOwningThread(); + mGetAll = true; + + return HandlePreprocessInternal(aPreprocessInfos); +} + +nsresult BackgroundRequestChild::HandlePreprocessInternal( + const nsTArray<PreprocessInfo>& aPreprocessInfos) { + AssertIsOnOwningThread(); + + IDBDatabase* database = mTransaction->Database(); + + const uint32_t count = aPreprocessInfos.Length(); + + mCloneInfos.SetLength(count); + + // TODO: Since we use the stream transport service, this can spawn 25 threads + // and has the potential to cause some annoying browser hiccups. + // Consider using a single thread or a very small threadpool. + for (uint32_t index = 0; index < count; index++) { + const PreprocessInfo& preprocessInfo = aPreprocessInfos[index]; + + const auto files = + DeserializeStructuredCloneFiles(database, preprocessInfo.files(), + /* aForPreprocess */ true); + + MOZ_ASSERT(files.Length() == 1); + + auto& preprocessHelper = mCloneInfos[index].mPreprocessHelper; + preprocessHelper = MakeRefPtr<PreprocessHelper>(index, this); + + nsresult rv = preprocessHelper->Init(files[0]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = preprocessHelper->Dispatch(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mRunningPreprocessHelpers++; + } + + return NS_OK; +} + +void BackgroundRequestChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + MaybeCollectGarbageOnIPCMessage(); + + for (auto& cloneInfo : mCloneInfos) { + const auto& preprocessHelper = cloneInfo.mPreprocessHelper; + + if (preprocessHelper) { + preprocessHelper->ClearActor(); + } + } + mCloneInfos.Clear(); + + if (mTransaction) { + mTransaction->AssertIsOnOwningThread(); + + mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ + aWhy == Deletion); +#ifdef DEBUG + mTransaction = nullptr; +#endif + } +} + +mozilla::ipc::IPCResult BackgroundRequestChild::Recv__delete__( + RequestResponse&& aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTransaction); + + MaybeCollectGarbageOnIPCMessage(); + + if (mTransaction->IsAborted()) { + // Always fire an "error" event with ABORT_ERR if the transaction was + // aborted, even if the request succeeded or failed with another error. + HandleResponse(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); + } else { + switch (aResponse.type()) { + case RequestResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case RequestResponse::TObjectStoreAddResponse: + HandleResponse(aResponse.get_ObjectStoreAddResponse().key()); + break; + + case RequestResponse::TObjectStorePutResponse: + HandleResponse(aResponse.get_ObjectStorePutResponse().key()); + break; + + case RequestResponse::TObjectStoreGetResponse: + HandleResponse( + std::move(aResponse.get_ObjectStoreGetResponse().cloneInfo())); + break; + + case RequestResponse::TObjectStoreGetKeyResponse: + HandleResponse(aResponse.get_ObjectStoreGetKeyResponse().key()); + break; + + case RequestResponse::TObjectStoreGetAllResponse: + HandleResponse( + std::move(aResponse.get_ObjectStoreGetAllResponse().cloneInfos())); + break; + + case RequestResponse::TObjectStoreGetAllKeysResponse: + HandleResponse(aResponse.get_ObjectStoreGetAllKeysResponse().keys()); + break; + + case RequestResponse::TObjectStoreDeleteResponse: + case RequestResponse::TObjectStoreClearResponse: + HandleResponse(JS::UndefinedHandleValue); + break; + + case RequestResponse::TObjectStoreCountResponse: + HandleResponse(aResponse.get_ObjectStoreCountResponse().count()); + break; + + case RequestResponse::TIndexGetResponse: + HandleResponse(std::move(aResponse.get_IndexGetResponse().cloneInfo())); + break; + + case RequestResponse::TIndexGetKeyResponse: + HandleResponse(aResponse.get_IndexGetKeyResponse().key()); + break; + + case RequestResponse::TIndexGetAllResponse: + HandleResponse( + std::move(aResponse.get_IndexGetAllResponse().cloneInfos())); + break; + + case RequestResponse::TIndexGetAllKeysResponse: + HandleResponse(aResponse.get_IndexGetAllKeysResponse().keys()); + break; + + case RequestResponse::TIndexCountResponse: + HandleResponse(aResponse.get_IndexCountResponse().count()); + break; + + default: + return IPC_FAIL(this, "Unknown response type!"); + } + } + + mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ true); + + // Null this out so that we don't try to call OnRequestFinished() again in + // ActorDestroy. + mTransaction = nullptr; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundRequestChild::RecvPreprocess( + const PreprocessParams& aParams) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTransaction); + + MaybeCollectGarbageOnIPCMessage(); + + nsresult rv; + + switch (aParams.type()) { + case PreprocessParams::TObjectStoreGetPreprocessParams: { + const auto& params = aParams.get_ObjectStoreGetPreprocessParams(); + + rv = HandlePreprocess(params.preprocessInfo()); + + break; + } + + case PreprocessParams::TObjectStoreGetAllPreprocessParams: { + const auto& params = aParams.get_ObjectStoreGetAllPreprocessParams(); + + rv = HandlePreprocess(params.preprocessInfos()); + + break; + } + + default: + return IPC_FAIL(this, "Unknown params type!"); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + QM_WARNONLY_TRY(OkIf(SendContinue(rv))); + } + + return IPC_OK(); +} + +nsresult BackgroundRequestChild::PreprocessHelper::Init( + const StructuredCloneFileChild& aFile) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aFile.HasBlob()); + MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eStructuredClone); + MOZ_ASSERT(mState == State::Initial); + + // The stream transport service is used for asynchronous processing. It has a + // threadpool with a high cap of 25 threads. Fortunately, the service can be + // used on workers too. + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + // We use a TaskQueue here in order to be sure that the events are dispatched + // in the correct order. This is not guaranteed in case we use the I/O thread + // directly. + mTaskQueue = TaskQueue::Create(target.forget(), "BackgroundRequestChild"); + + ErrorResult errorResult; + + nsCOMPtr<nsIInputStream> stream; + // XXX After Bug 1620560, MutableBlob is not needed here anymore. + aFile.MutableBlob().CreateInputStream(getter_AddRefs(stream), errorResult); + if (NS_WARN_IF(errorResult.Failed())) { + return errorResult.StealNSResult(); + } + + mStream = std::move(stream); + + mCloneData = MakeUnique<JSStructuredCloneData>( + JS::StructuredCloneScope::DifferentProcessForIndexedDB); + + return NS_OK; +} + +nsresult BackgroundRequestChild::PreprocessHelper::Dispatch() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::Initial); + + nsresult rv = mTaskQueue->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult BackgroundRequestChild::PreprocessHelper::Start() { + MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mStream); + MOZ_ASSERT(mState == State::Initial); + + nsresult rv; + + PRFileDesc* fileDesc = GetFileDescriptorFromStream(mStream); + if (fileDesc) { + rv = ProcessStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + mState = State::WaitingForStreamReady; + + nsCOMPtr<nsIAsyncFileMetadata> asyncFileMetadata = do_QueryInterface(mStream); + if (asyncFileMetadata) { + rv = asyncFileMetadata->AsyncFileMetadataWait(this, mTaskQueue); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream); + if (!asyncStream) { + return NS_ERROR_NO_INTERFACE; + } + + rv = asyncStream->AsyncWait(this, 0, 0, mTaskQueue); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult BackgroundRequestChild::PreprocessHelper::ProcessStream() { + MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mStream); + MOZ_ASSERT(mState == State::Initial || + mState == State::WaitingForStreamReady); + + // We need to get the internal stream (which is an nsFileInputStream) because + // SnappyUncompressInputStream doesn't support reading from async input + // streams. + + nsCOMPtr<mozIRemoteLazyInputStream> blobInputStream = + do_QueryInterface(mStream); + MOZ_ASSERT(blobInputStream); + + nsCOMPtr<nsIInputStream> internalInputStream; + MOZ_ALWAYS_SUCCEEDS( + blobInputStream->TakeInternalStream(getter_AddRefs(internalInputStream))); + MOZ_ASSERT(internalInputStream); + + QM_TRY(MOZ_TO_RESULT( + SnappyUncompressStructuredCloneData(*internalInputStream, *mCloneData))); + + mState = State::Finishing; + + QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL))); + + return NS_OK; +} + +void BackgroundRequestChild::PreprocessHelper::Finish() { + AssertIsOnOwningThread(); + + if (mActor) { + if (NS_SUCCEEDED(mResultCode)) { + mActor->OnPreprocessFinished(mCloneDataIndex, std::move(mCloneData)); + + MOZ_ASSERT(!mCloneData); + } else { + mActor->OnPreprocessFailed(mCloneDataIndex, mResultCode); + } + } + + mState = State::Completed; +} + +NS_IMPL_ISUPPORTS_INHERITED(BackgroundRequestChild::PreprocessHelper, + DiscardableRunnable, nsIInputStreamCallback, + nsIFileMetadataCallback) + +NS_IMETHODIMP +BackgroundRequestChild::PreprocessHelper::Run() { + nsresult rv; + + switch (mState) { + case State::Initial: + rv = Start(); + break; + + case State::WaitingForStreamReady: + rv = ProcessStream(); + break; + + case State::Finishing: + Finish(); + return NS_OK; + + default: + MOZ_CRASH("Bad state!"); + } + + if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) { + if (NS_SUCCEEDED(mResultCode)) { + mResultCode = rv; + } + + // Must set mState before dispatching otherwise we will race with the owning + // thread. + mState = State::Finishing; + + if (IsOnOwningThread()) { + Finish(); + } else { + MOZ_ALWAYS_SUCCEEDS( + mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundRequestChild::PreprocessHelper::OnInputStreamReady( + nsIAsyncInputStream* aStream) { + MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mState == State::WaitingForStreamReady); + + MOZ_ALWAYS_SUCCEEDS(this->Run()); + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundRequestChild::PreprocessHelper::OnFileMetadataReady( + nsIAsyncFileMetadata* aObject) { + MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mState == State::WaitingForStreamReady); + + MOZ_ALWAYS_SUCCEEDS(this->Run()); + + return NS_OK; +} + +/******************************************************************************* + * BackgroundCursorChild + ******************************************************************************/ + +BackgroundCursorChildBase::BackgroundCursorChildBase( + const NotNull<IDBRequest*> aRequest, const Direction aDirection) + : mRequest(aRequest), + mTransaction(aRequest->MaybeTransactionRef()), + mStrongRequest(aRequest), + mDirection(aDirection) { + MOZ_ASSERT(mTransaction); +} + +MovingNotNull<RefPtr<IDBRequest>> BackgroundCursorChildBase::AcquireRequest() + const { + AssertIsOnOwningThread(); + + // XXX This could be encapsulated by NotNull + return WrapNotNullUnchecked(RefPtr{mRequest->get()}); +} + +template <IDBCursorType CursorType> +BackgroundCursorChild<CursorType>::BackgroundCursorChild( + const NotNull<IDBRequest*> aRequest, SourceType* aSource, + Direction aDirection) + : BackgroundCursorChildBase(aRequest, aDirection), + mSource(WrapNotNull(aSource)), + mCursor(nullptr), + mInFlightResponseInvalidationNeeded(false) { + aSource->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(indexedDB::BackgroundCursorChild<CursorType>); +} + +template <IDBCursorType CursorType> +BackgroundCursorChild<CursorType>::~BackgroundCursorChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundCursorChild<CursorType>); +} + +template <IDBCursorType CursorType> +SafeRefPtr<BackgroundCursorChild<CursorType>> +BackgroundCursorChild<CursorType>::SafeRefPtrFromThis() { + return BackgroundCursorChildBase::SafeRefPtrFromThis() + .template downcast<BackgroundCursorChild>(); +} + +template <IDBCursorType CursorType> +void BackgroundCursorChild<CursorType>::SendContinueInternal( + const CursorRequestParams& aParams, + const CursorData<CursorType>& aCurrentData) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + MOZ_ASSERT(mTransaction); + MOZ_ASSERT(mCursor); + MOZ_ASSERT(!mStrongRequest); + MOZ_ASSERT(!mStrongCursor); + + // Make sure all our DOM objects stay alive. + mStrongCursor = mCursor; + + MOZ_ASSERT(GetRequest()->ReadyState() == IDBRequestReadyState::Done); + GetRequest()->Reset(); + + mTransaction->OnNewRequest(); + + CursorRequestParams params = aParams; + Key currentKey = aCurrentData.mKey; + Key currentObjectStoreKey; + // TODO: This is still not nice. + if constexpr (!CursorTypeTraits<CursorType>::IsObjectStoreCursor) { + currentObjectStoreKey = aCurrentData.mObjectStoreKey; + } + + switch (params.type()) { + case CursorRequestParams::TContinueParams: { + const auto& key = params.get_ContinueParams().key(); + if (key.IsUnset()) { + break; + } + + // Discard cache entries before the target key. + DiscardCachedResponses( + [&key, isLocaleAware = mCursor->IsLocaleAware(), + keyOperator = GetKeyOperator(mDirection), + transactionSerialNumber = mTransaction->LoggingSerialNumber(), + requestSerialNumber = GetRequest()->LoggingSerialNumber()]( + const auto& currentCachedResponse) { + // This duplicates the logic from the parent. We could avoid this + // duplication if we invalidated the cached records always for any + // continue-with-key operation, but would lose the benefits of + // preloading then. + const auto& cachedSortKey = + currentCachedResponse.GetSortKey(isLocaleAware); + const bool discard = !(cachedSortKey.*keyOperator)(key); + if (discard) { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Continue to key %s, discarding cached key %s/%s", + "Continue, discarding%.0s%.0s%.0s", transactionSerialNumber, + requestSerialNumber, key.GetBuffer().get(), + cachedSortKey.GetBuffer().get(), + currentCachedResponse.GetObjectStoreKeyForLogging()); + } else { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Continue to key %s, keeping cached key %s/%s and " + "further", + "Continue, keeping%.0s%.0s%.0s", transactionSerialNumber, + requestSerialNumber, key.GetBuffer().get(), + cachedSortKey.GetBuffer().get(), + currentCachedResponse.GetObjectStoreKeyForLogging()); + } + + return discard; + }); + + break; + } + + case CursorRequestParams::TContinuePrimaryKeyParams: { + if constexpr (!CursorTypeTraits<CursorType>::IsObjectStoreCursor) { + const auto& key = params.get_ContinuePrimaryKeyParams().key(); + const auto& primaryKey = + params.get_ContinuePrimaryKeyParams().primaryKey(); + if (key.IsUnset() || primaryKey.IsUnset()) { + break; + } + + // Discard cache entries before the target key. + DiscardCachedResponses([&key, &primaryKey, + isLocaleAware = mCursor->IsLocaleAware(), + keyCompareOperator = GetKeyOperator(mDirection), + transactionSerialNumber = + mTransaction->LoggingSerialNumber(), + requestSerialNumber = + GetRequest()->LoggingSerialNumber()]( + const auto& currentCachedResponse) { + // This duplicates the logic from the parent. We could avoid this + // duplication if we invalidated the cached records always for any + // continue-with-key operation, but would lose the benefits of + // preloading then. + const auto& cachedSortKey = + currentCachedResponse.GetSortKey(isLocaleAware); + const auto& cachedSortPrimaryKey = + currentCachedResponse.mObjectStoreKey; + + const bool discard = + (cachedSortKey == key && + !(cachedSortPrimaryKey.*keyCompareOperator)(primaryKey)) || + !(cachedSortKey.*keyCompareOperator)(key); + + if (discard) { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Continue to key %s with primary key %s, discarding " + "cached key %s with cached primary key %s", + "Continue, discarding%.0s%.0s%.0s%.0s", transactionSerialNumber, + requestSerialNumber, key.GetBuffer().get(), + primaryKey.GetBuffer().get(), cachedSortKey.GetBuffer().get(), + cachedSortPrimaryKey.GetBuffer().get()); + } else { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Continue to key %s with primary key %s, keeping " + "cached key %s with cached primary key %s and further", + "Continue, keeping%.0s%.0s%.0s%.0s", transactionSerialNumber, + requestSerialNumber, key.GetBuffer().get(), + primaryKey.GetBuffer().get(), cachedSortKey.GetBuffer().get(), + cachedSortPrimaryKey.GetBuffer().get()); + } + + return discard; + }); + } else { + MOZ_CRASH("Shouldn't get here"); + } + + break; + } + + case CursorRequestParams::TAdvanceParams: { + uint32_t& advanceCount = params.get_AdvanceParams().count(); + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Advancing %" PRIu32 " records", "Advancing %" PRIu32, + mTransaction->LoggingSerialNumber(), + GetRequest()->LoggingSerialNumber(), advanceCount); + + // Discard cache entries. + DiscardCachedResponses([&advanceCount, ¤tKey, + ¤tObjectStoreKey]( + const auto& currentCachedResponse) { + const bool res = advanceCount > 1; + if (res) { + --advanceCount; + + // TODO: We only need to update currentKey on the last entry, the + // others are overwritten in the next iteration anyway. + currentKey = currentCachedResponse.mKey; + if constexpr (!CursorTypeTraits<CursorType>::IsObjectStoreCursor) { + currentObjectStoreKey = currentCachedResponse.mObjectStoreKey; + } else { + Unused << currentObjectStoreKey; + } + } + return res; + }); + break; + } + + default: + MOZ_CRASH("Should never get here!"); + } + + if (!mCachedResponses.empty()) { + // We need to remove the response here from mCachedResponses, since when + // requests are interleaved, other events may be processed before + // CompleteContinueRequestFromCache, which may modify mCachedResponses. + mDelayedResponses.emplace_back(std::move(mCachedResponses.front())); + mCachedResponses.pop_front(); + + // We cannot send the response right away, as we must preserve the request + // order. Dispatching a DelayedActionRunnable only partially addresses this. + // This is accompanied by invalidating cached entries at proper locations to + // make it correct. To avoid this, further changes are necessary, see Bug + // 1580499. + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread( + MakeAndAddRef<DelayedActionRunnable<BackgroundCursorChild<CursorType>>>( + SafeRefPtrFromThis(), + &BackgroundCursorChild::CompleteContinueRequestFromCache))); + + // TODO: Could we preload further entries in the background when the size of + // mCachedResponses falls under some threshold? Or does the response + // handling model disallow this? + } else { + MOZ_ALWAYS_TRUE(PBackgroundIDBCursorChild::SendContinue( + params, currentKey, currentObjectStoreKey)); + } +} + +template <IDBCursorType CursorType> +void BackgroundCursorChild<CursorType>::CompleteContinueRequestFromCache() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTransaction); + MOZ_ASSERT(mCursor); + MOZ_ASSERT(mStrongCursor); + MOZ_ASSERT(!mDelayedResponses.empty()); + MOZ_ASSERT(mCursor->GetType() == CursorType); + + const RefPtr<IDBCursor> cursor = std::move(mStrongCursor); + + mCursor->Reset(std::move(mDelayedResponses.front())); + mDelayedResponses.pop_front(); + + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Consumed 1 cached response, %zu cached responses remaining", + "Consumed cached response, %zu remaining", + mTransaction->LoggingSerialNumber(), GetRequest()->LoggingSerialNumber(), + mDelayedResponses.size() + mCachedResponses.size()); + + SetResultAndDispatchSuccessEvent( + GetRequest(), + mTransaction + ? SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}} + : nullptr, + *cursor); + + mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ true); +} + +template <IDBCursorType CursorType> +void BackgroundCursorChild<CursorType>::SendDeleteMeInternal() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mStrongRequest); + MOZ_ASSERT(!mStrongCursor); + + mRequest.destroy(); + mTransaction = Nothing(); + // TODO: The things until here could be pulled up to + // BackgroundCursorChildBase. + + mSource.destroy(); + + if (mCursor) { + mCursor->ClearBackgroundActor(); + mCursor = nullptr; + + MOZ_ALWAYS_TRUE(PBackgroundIDBCursorChild::SendDeleteMe()); + } +} + +template <IDBCursorType CursorType> +void BackgroundCursorChild<CursorType>::InvalidateCachedResponses() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTransaction); + MOZ_ASSERT(mRequest); + + // TODO: With more information on the reason for the invalidation, we might + // only selectively invalidate cached responses. If the reason is an updated + // value, we do not need to care for key-only cursors. If the key of the + // changed entry is not in the remaining range of the cursor, we also do not + // need to care, etc. + + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Invalidating all %zu cached responses", "Invalidating %zu", + mTransaction->LoggingSerialNumber(), GetRequest()->LoggingSerialNumber(), + mCachedResponses.size()); + + mCachedResponses.clear(); + + // We only hold a strong cursor reference in mStrongCursor when + // continue()/similar has been called. In those cases we expect a response + // that will be received in the future, and it may include prefetched data + // that needs to be discarded. + if (mStrongCursor) { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Setting flag to invalidate in-flight responses", + "Set flag to invalidate in-flight responses", + mTransaction->LoggingSerialNumber(), + GetRequest()->LoggingSerialNumber()); + + mInFlightResponseInvalidationNeeded = true; + } +} + +template <IDBCursorType CursorType> +template <typename Condition> +void BackgroundCursorChild<CursorType>::DiscardCachedResponses( + const Condition& aConditionFunc) { + size_t discardedCount = 0; + while (!mCachedResponses.empty() && + aConditionFunc(mCachedResponses.front())) { + mCachedResponses.pop_front(); + ++discardedCount; + } + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Discarded %zu cached responses, %zu remaining", + "Discarded %zu; remaining %zu", mTransaction->LoggingSerialNumber(), + GetRequest()->LoggingSerialNumber(), discardedCount, + mCachedResponses.size()); +} + +BackgroundCursorChildBase::~BackgroundCursorChildBase() = default; + +void BackgroundCursorChildBase::HandleResponse(nsresult aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB); + MOZ_ASSERT(mRequest); + MOZ_ASSERT(mTransaction); + MOZ_ASSERT(!mStrongRequest); + MOZ_ASSERT(!mStrongCursor); + + DispatchErrorEvent( + GetRequest(), aResponse, + SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}}); +} + +template <IDBCursorType CursorType> +void BackgroundCursorChild<CursorType>::HandleResponse( + const void_t& aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + MOZ_ASSERT(mTransaction); + MOZ_ASSERT(!mStrongRequest); + MOZ_ASSERT(!mStrongCursor); + + if (mCursor) { + mCursor->Reset(); + } + + SetResultAndDispatchSuccessEvent( + GetRequest(), + mTransaction + ? SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}} + : nullptr, + JS::NullHandleValue); + + if (!mCursor) { + MOZ_ALWAYS_SUCCEEDS(this->GetActorEventTarget()->Dispatch( + MakeAndAddRef<DelayedActionRunnable<BackgroundCursorChild<CursorType>>>( + SafeRefPtrFromThis(), &BackgroundCursorChild::SendDeleteMeInternal), + NS_DISPATCH_NORMAL)); + } +} + +template <IDBCursorType CursorType> +template <typename... Args> +RefPtr<IDBCursor> +BackgroundCursorChild<CursorType>::HandleIndividualCursorResponse( + const bool aUseAsCurrentResult, Args&&... aArgs) { + if (mCursor) { + if (aUseAsCurrentResult) { + mCursor->Reset(CursorData<CursorType>{std::forward<Args>(aArgs)...}); + } else { + mCachedResponses.emplace_back(std::forward<Args>(aArgs)...); + } + return nullptr; + } + + MOZ_ASSERT(aUseAsCurrentResult); + + // TODO: This still looks quite dangerous to me. Why is mCursor not a + // RefPtr? + auto newCursor = IDBCursor::Create(this, std::forward<Args>(aArgs)...); + mCursor = newCursor; + return newCursor; +} + +template <IDBCursorType CursorType> +template <typename Func> +void BackgroundCursorChild<CursorType>::HandleMultipleCursorResponses( + nsTArray<ResponseType>&& aResponses, const Func& aHandleRecord) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + MOZ_ASSERT(mTransaction); + MOZ_ASSERT(!mStrongRequest); + MOZ_ASSERT(!mStrongCursor); + MOZ_ASSERT(aResponses.Length() > 0); + + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Received %zu cursor responses", "Received %zu", + mTransaction->LoggingSerialNumber(), GetRequest()->LoggingSerialNumber(), + aResponses.Length()); + MOZ_ASSERT_IF(aResponses.Length() > 1, mCachedResponses.empty()); + + // If a new cursor is created, we need to keep a reference to it until the + // SetResultAndDispatchSuccessEvent creates a DOM Binding. + RefPtr<IDBCursor> strongNewCursor; + + bool isFirst = true; + for (auto& response : aResponses) { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Processing response for key %s", "Processing%.0s", + mTransaction->LoggingSerialNumber(), + GetRequest()->LoggingSerialNumber(), response.key().GetBuffer().get()); + + // TODO: At the moment, we only send a cursor request to the parent if + // requested by the user code. Therefore, the first result is always used + // as the current result, and the potential extra results are cached. If + // we extended this towards preloading in the background, all results + // might need to be cached. + auto maybeNewCursor = + aHandleRecord(/* aUseAsCurrentResult */ isFirst, std::move(response)); + if (maybeNewCursor) { + MOZ_ASSERT(!strongNewCursor); + strongNewCursor = std::move(maybeNewCursor); + } + isFirst = false; + + if (mInFlightResponseInvalidationNeeded) { + IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( + "PRELOAD: Discarding remaining responses since " + "mInFlightResponseInvalidationNeeded is set", + "Discarding responses", mTransaction->LoggingSerialNumber(), + GetRequest()->LoggingSerialNumber()); + + mInFlightResponseInvalidationNeeded = false; + break; + } + } + + SetResultAndDispatchSuccessEvent( + GetRequest(), + mTransaction + ? SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}} + : nullptr, + *static_cast<IDBCursor*>(mCursor)); +} + +template <IDBCursorType CursorType> +void BackgroundCursorChild<CursorType>::HandleResponse( + nsTArray<ResponseType>&& aResponses) { + AssertIsOnOwningThread(); + + if constexpr (CursorType == IDBCursorType::ObjectStore || + CursorType == IDBCursorType::Index) { + MOZ_ASSERT(mTransaction); + + if (!mTransaction->Database()->GetOwnerGlobal()) { + // Ignore the response, since we have already been disconnected from the + // global. + return; + } + } + + if constexpr (CursorType == IDBCursorType::ObjectStore) { + HandleMultipleCursorResponses( + std::move(aResponses), [this](const bool useAsCurrentResult, + ObjectStoreCursorResponse&& response) { + // TODO: Maybe move the deserialization of the clone-read-info into + // the cursor, so that it is only done for records actually accessed, + // which might not be the case for all cached records. + return HandleIndividualCursorResponse( + useAsCurrentResult, std::move(response.key()), + DeserializeStructuredCloneReadInfo( + std::move(response.cloneInfo()), mTransaction->Database(), + PreprocessingNotSupported)); + }); + } + if constexpr (CursorType == IDBCursorType::ObjectStoreKey) { + HandleMultipleCursorResponses( + std::move(aResponses), [this](const bool useAsCurrentResult, + ObjectStoreKeyCursorResponse&& response) { + return HandleIndividualCursorResponse(useAsCurrentResult, + std::move(response.key())); + }); + } + if constexpr (CursorType == IDBCursorType::Index) { + HandleMultipleCursorResponses( + std::move(aResponses), + [this](const bool useAsCurrentResult, IndexCursorResponse&& response) { + return HandleIndividualCursorResponse( + useAsCurrentResult, std::move(response.key()), + std::move(response.sortKey()), std::move(response.objectKey()), + DeserializeStructuredCloneReadInfo( + std::move(response.cloneInfo()), mTransaction->Database(), + PreprocessingNotSupported)); + }); + } + if constexpr (CursorType == IDBCursorType::IndexKey) { + HandleMultipleCursorResponses( + std::move(aResponses), [this](const bool useAsCurrentResult, + IndexKeyCursorResponse&& response) { + return HandleIndividualCursorResponse( + useAsCurrentResult, std::move(response.key()), + std::move(response.sortKey()), std::move(response.objectKey())); + }); + } +} + +template <IDBCursorType CursorType> +void BackgroundCursorChild<CursorType>::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(aWhy == Deletion, !mStrongRequest); + MOZ_ASSERT_IF(aWhy == Deletion, !mStrongCursor); + + MaybeCollectGarbageOnIPCMessage(); + + if (mStrongRequest && !mStrongCursor && mTransaction) { + mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ + aWhy == Deletion); + } + + if (mCursor) { + mCursor->ClearBackgroundActor(); +#ifdef DEBUG + mCursor = nullptr; +#endif + } + +#ifdef DEBUG + mRequest.maybeDestroy(); + mTransaction = Nothing(); + mSource.maybeDestroy(); +#endif +} + +template <IDBCursorType CursorType> +mozilla::ipc::IPCResult BackgroundCursorChild<CursorType>::RecvResponse( + CursorResponse&& aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aResponse.type() != CursorResponse::T__None); + MOZ_ASSERT(mRequest); + MOZ_ASSERT(mTransaction); + MOZ_ASSERT_IF(mCursor, mStrongCursor); + MOZ_ASSERT_IF(!mCursor, mStrongRequest); + + MaybeCollectGarbageOnIPCMessage(); + + const RefPtr<IDBRequest> request = std::move(mStrongRequest); + Unused << request; // XXX see Bug 1605075 + const RefPtr<IDBCursor> cursor = std::move(mStrongCursor); + Unused << cursor; // XXX see Bug 1605075 + + const auto transaction = + SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}}; + + switch (aResponse.type()) { + case CursorResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case CursorResponse::Tvoid_t: + HandleResponse(aResponse.get_void_t()); + break; + + case CursorResponse::TArrayOfObjectStoreCursorResponse: + if constexpr (CursorType == IDBCursorType::ObjectStore) { + HandleResponse( + std::move(aResponse.get_ArrayOfObjectStoreCursorResponse())); + } else { + MOZ_CRASH("Response type mismatch"); + } + break; + + case CursorResponse::TArrayOfObjectStoreKeyCursorResponse: + if constexpr (CursorType == IDBCursorType::ObjectStoreKey) { + HandleResponse( + std::move(aResponse.get_ArrayOfObjectStoreKeyCursorResponse())); + } else { + MOZ_CRASH("Response type mismatch"); + } + break; + + case CursorResponse::TArrayOfIndexCursorResponse: + if constexpr (CursorType == IDBCursorType::Index) { + HandleResponse(std::move(aResponse.get_ArrayOfIndexCursorResponse())); + } else { + MOZ_CRASH("Response type mismatch"); + } + break; + + case CursorResponse::TArrayOfIndexKeyCursorResponse: + if constexpr (CursorType == IDBCursorType::IndexKey) { + HandleResponse( + std::move(aResponse.get_ArrayOfIndexKeyCursorResponse())); + } else { + MOZ_CRASH("Response type mismatch"); + } + break; + + default: + MOZ_CRASH("Should never get here!"); + } + + transaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ true); + + return IPC_OK(); +} + +template class BackgroundCursorChild<IDBCursorType::ObjectStore>; +template class BackgroundCursorChild<IDBCursorType::ObjectStoreKey>; +template class BackgroundCursorChild<IDBCursorType::Index>; +template class BackgroundCursorChild<IDBCursorType::IndexKey>; + +template <typename T> +NS_IMETHODIMP DelayedActionRunnable<T>::Run() { + MOZ_ASSERT(mActor); + mActor->AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + MOZ_ASSERT(mActionFunc); + + ((*mActor).*mActionFunc)(); + + mActor = nullptr; + mRequest = nullptr; + + return NS_OK; +} + +template <typename T> +nsresult DelayedActionRunnable<T>::Cancel() { + if (NS_WARN_IF(!mActor)) { + return NS_ERROR_UNEXPECTED; + } + + // This must always run to clean up our state. + Run(); + + return NS_OK; +} + +/******************************************************************************* + * BackgroundFileHandleChild + ******************************************************************************/ + +BackgroundFileHandleChild::BackgroundFileHandleChild(IDBFileHandle* aFileHandle) + : mTemporaryStrongFileHandle(aFileHandle), mFileHandle(aFileHandle) { + MOZ_ASSERT(aFileHandle); + aFileHandle->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(BackgroundFileHandleChild); +} + +BackgroundFileHandleChild::~BackgroundFileHandleChild() { + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(BackgroundFileHandleChild); +} + +#ifdef DEBUG + +void BackgroundFileHandleChild::AssertIsOnOwningThread() const { + static_cast<BackgroundMutableFileChild*>(Manager())->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void BackgroundFileHandleChild::SendDeleteMeInternal() { + AssertIsOnOwningThread(); + + if (mFileHandle) { + NoteActorDestroyed(); + + MOZ_ALWAYS_TRUE(PBackgroundFileHandleChild::SendDeleteMe()); + } +} + +void BackgroundFileHandleChild::NoteActorDestroyed() { + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(mTemporaryStrongFileHandle, mFileHandle); + + if (mFileHandle) { + mFileHandle->ClearBackgroundActor(); + + // Normally this would be DEBUG-only but NoteActorDestroyed is also called + // from SendDeleteMeInternal. In that case we're going to receive an + // actual ActorDestroy call later and we don't want to touch a dead + // object. + mTemporaryStrongFileHandle = nullptr; + mFileHandle = nullptr; + } +} + +void BackgroundFileHandleChild::NoteComplete() { + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(mFileHandle, mTemporaryStrongFileHandle); + + mTemporaryStrongFileHandle = nullptr; +} + +void BackgroundFileHandleChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + NoteActorDestroyed(); +} + +mozilla::ipc::IPCResult BackgroundFileHandleChild::RecvComplete( + const bool aAborted) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mFileHandle); + + mFileHandle->FireCompleteOrAbortEvents(aAborted); + + NoteComplete(); + return IPC_OK(); +} + +PBackgroundFileRequestChild* +BackgroundFileHandleChild::AllocPBackgroundFileRequestChild( + const FileRequestParams& aParams) { + MOZ_CRASH( + "PBackgroundFileRequestChild actors should be manually " + "constructed!"); +} + +bool BackgroundFileHandleChild::DeallocPBackgroundFileRequestChild( + PBackgroundFileRequestChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundFileRequestChild*>(aActor); + return true; +} + +/******************************************************************************* + * BackgroundFileRequestChild + ******************************************************************************/ + +BackgroundFileRequestChild::BackgroundFileRequestChild( + IDBFileRequest* aFileRequest) + : mFileRequest(aFileRequest), + mFileHandle(aFileRequest->GetFileHandle()), + mActorDestroyed(false) { + MOZ_ASSERT(aFileRequest); + aFileRequest->AssertIsOnOwningThread(); + MOZ_ASSERT(mFileHandle); + mFileHandle->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(BackgroundFileRequestChild); +} + +BackgroundFileRequestChild::~BackgroundFileRequestChild() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mFileHandle); + + MOZ_COUNT_DTOR(BackgroundFileRequestChild); +} + +#ifdef DEBUG + +void BackgroundFileRequestChild::AssertIsOnOwningThread() const { + MOZ_ASSERT(mFileRequest); + mFileRequest->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void BackgroundFileRequestChild::HandleResponse(nsresult aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_FILEHANDLE); + MOZ_ASSERT(mFileHandle); + + DispatchFileHandleErrorEvent(mFileRequest, aResponse, mFileHandle); +} + +void BackgroundFileRequestChild::HandleResponse(const nsCString& aResponse) { + AssertIsOnOwningThread(); + + SetFileHandleResultAndDispatchSuccessEvent(mFileRequest, mFileHandle, + &aResponse); +} + +void BackgroundFileRequestChild::HandleResponse( + const FileRequestMetadata& aResponse) { + AssertIsOnOwningThread(); + + SetFileHandleResultAndDispatchSuccessEvent(mFileRequest, mFileHandle, + &aResponse); +} + +void BackgroundFileRequestChild::HandleResponse( + const JS::Handle<JS::Value> aResponse) { + AssertIsOnOwningThread(); + + SetFileHandleResultAndDispatchSuccessEvent(mFileRequest, mFileHandle, + &aResponse); +} + +void BackgroundFileRequestChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + MOZ_ASSERT(!mActorDestroyed); + + mActorDestroyed = true; + + if (mFileHandle) { + mFileHandle->AssertIsOnOwningThread(); + + mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ + aWhy == Deletion); + +#ifdef DEBUG + mFileHandle = nullptr; +#endif + } +} + +mozilla::ipc::IPCResult BackgroundFileRequestChild::Recv__delete__( + const FileRequestResponse& aResponse) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mFileRequest); + MOZ_ASSERT(mFileHandle); + + if (mFileHandle->IsAborted()) { + // Always handle an "error" with ABORT_ERR if the file handle was aborted, + // even if the request succeeded or failed with another error. + HandleResponse(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR); + } else { + switch (aResponse.type()) { + case FileRequestResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case FileRequestResponse::TFileRequestReadResponse: + HandleResponse(aResponse.get_FileRequestReadResponse().data()); + break; + + case FileRequestResponse::TFileRequestWriteResponse: + case FileRequestResponse::TFileRequestTruncateResponse: + case FileRequestResponse::TFileRequestFlushResponse: + HandleResponse(JS::UndefinedHandleValue); + break; + + case FileRequestResponse::TFileRequestGetMetadataResponse: + HandleResponse( + aResponse.get_FileRequestGetMetadataResponse().metadata()); + break; + + default: + return IPC_FAIL(this, "Unknown response type!"); + } + } + + mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ true); + + // Null this out so that we don't try to call OnRequestFinished() again in + // ActorDestroy. + mFileHandle = nullptr; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundFileRequestChild::RecvProgress( + const uint64_t aProgress, const uint64_t aProgressMax) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mFileRequest); + + mFileRequest->FireProgressEvent(aProgress, aProgressMax); + + return IPC_OK(); +} + +/******************************************************************************* + * BackgroundUtilsChild + ******************************************************************************/ + +BackgroundUtilsChild::BackgroundUtilsChild(IndexedDatabaseManager* aManager) + : mManager(aManager) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aManager); + + MOZ_COUNT_CTOR(indexedDB::BackgroundUtilsChild); +} + +BackgroundUtilsChild::~BackgroundUtilsChild() { + MOZ_COUNT_DTOR(indexedDB::BackgroundUtilsChild); +} + +void BackgroundUtilsChild::SendDeleteMeInternal() { + AssertIsOnOwningThread(); + + if (mManager) { + mManager->ClearBackgroundActor(); + mManager = nullptr; + + MOZ_ALWAYS_TRUE(PBackgroundIndexedDBUtilsChild::SendDeleteMe()); + } +} + +void BackgroundUtilsChild::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + + if (mManager) { + mManager->ClearBackgroundActor(); +#ifdef DEBUG + mManager = nullptr; +#endif + } +} + +} // namespace dom::indexedDB +} // namespace mozilla |