summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/IDBObjectStore.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/indexedDB/IDBObjectStore.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/indexedDB/IDBObjectStore.cpp')
-rw-r--r--dom/indexedDB/IDBObjectStore.cpp1740
1 files changed, 1740 insertions, 0 deletions
diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp
new file mode 100644
index 0000000000..9a7ef25fc4
--- /dev/null
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -0,0 +1,1740 @@
+/* -*- 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 "IDBObjectStore.h"
+
+#include <numeric>
+#include <utility>
+
+#include "IDBCursorType.h"
+#include "IDBDatabase.h"
+#include "IDBEvents.h"
+#include "IDBFactory.h"
+#include "IDBIndex.h"
+#include "IDBKeyRange.h"
+#include "IDBRequest.h"
+#include "IDBTransaction.h"
+#include "IndexedDatabase.h"
+#include "IndexedDatabaseInlines.h"
+#include "IndexedDatabaseManager.h"
+#include "IndexedDBCommon.h"
+#include "KeyPath.h"
+#include "ProfilerHelpers.h"
+#include "ReportInternalError.h"
+#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
+#include "js/Class.h"
+#include "js/Date.h"
+#include "js/Object.h" // JS::GetClass
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById
+#include "js/StructuredClone.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/IDBObjectStoreBinding.h"
+#include "mozilla/dom/MemoryBlobImpl.h"
+#include "mozilla/dom/StreamBlobImpl.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsCOMPtr.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+// Include this last to avoid path problems on Windows.
+#include "ActorsChild.h"
+
+namespace mozilla::dom {
+
+using namespace mozilla::dom::indexedDB;
+using namespace mozilla::dom::quota;
+using namespace mozilla::ipc;
+
+namespace {
+
+Result<IndexUpdateInfo, nsresult> MakeIndexUpdateInfo(
+ const int64_t aIndexID, const Key& aKey, const nsCString& aLocale) {
+ IndexUpdateInfo indexUpdateInfo;
+ indexUpdateInfo.indexId() = aIndexID;
+ indexUpdateInfo.value() = aKey;
+ if (!aLocale.IsEmpty()) {
+ QM_TRY_UNWRAP(indexUpdateInfo.localizedValue(),
+ aKey.ToLocaleAwareKey(aLocale));
+ }
+ return indexUpdateInfo;
+}
+
+} // namespace
+
+struct IDBObjectStore::StructuredCloneWriteInfo {
+ JSAutoStructuredCloneBuffer mCloneBuffer;
+ nsTArray<StructuredCloneFileChild> mFiles;
+ IDBDatabase* mDatabase;
+ uint64_t mOffsetToKeyProp;
+
+ explicit StructuredCloneWriteInfo(IDBDatabase* aDatabase)
+ : mCloneBuffer(JS::StructuredCloneScope::DifferentProcessForIndexedDB,
+ nullptr, nullptr),
+ mDatabase(aDatabase),
+ mOffsetToKeyProp(0) {
+ MOZ_ASSERT(aDatabase);
+
+ MOZ_COUNT_CTOR(StructuredCloneWriteInfo);
+ }
+
+ StructuredCloneWriteInfo(StructuredCloneWriteInfo&& aCloneWriteInfo) noexcept
+ : mCloneBuffer(std::move(aCloneWriteInfo.mCloneBuffer)),
+ mFiles(std::move(aCloneWriteInfo.mFiles)),
+ mDatabase(aCloneWriteInfo.mDatabase),
+ mOffsetToKeyProp(aCloneWriteInfo.mOffsetToKeyProp) {
+ MOZ_ASSERT(mDatabase);
+
+ MOZ_COUNT_CTOR(StructuredCloneWriteInfo);
+
+ aCloneWriteInfo.mOffsetToKeyProp = 0;
+ }
+
+ MOZ_COUNTED_DTOR(StructuredCloneWriteInfo)
+};
+
+// Used by ValueWrapper::Clone to hold strong references to any blob-like
+// objects through the clone process. This is necessary because:
+// - The structured clone process may trigger content code via getters/other
+// which can potentially cause existing strong references to be dropped,
+// necessitating the clone to hold its own strong references.
+// - The structured clone can abort partway through, so it's necessary to track
+// what strong references have been acquired so that they can be freed even
+// if a de-serialization does not occur.
+struct IDBObjectStore::StructuredCloneInfo {
+ nsTArray<StructuredCloneFileChild> mFiles;
+};
+
+namespace {
+
+struct MOZ_STACK_CLASS GetAddInfoClosure final {
+ IDBObjectStore::StructuredCloneWriteInfo& mCloneWriteInfo;
+ JS::Handle<JS::Value> mValue;
+
+ GetAddInfoClosure(IDBObjectStore::StructuredCloneWriteInfo& aCloneWriteInfo,
+ JS::Handle<JS::Value> aValue)
+ : mCloneWriteInfo(aCloneWriteInfo), mValue(aValue) {
+ MOZ_COUNT_CTOR(GetAddInfoClosure);
+ }
+
+ MOZ_COUNTED_DTOR(GetAddInfoClosure)
+};
+
+MovingNotNull<RefPtr<IDBRequest>> GenerateRequest(
+ JSContext* aCx, IDBObjectStore* aObjectStore) {
+ MOZ_ASSERT(aObjectStore);
+ aObjectStore->AssertIsOnOwningThread();
+
+ auto transaction = aObjectStore->AcquireTransaction();
+ auto* const database = transaction->Database();
+
+ return IDBRequest::Create(aCx, aObjectStore, database,
+ std::move(transaction));
+}
+
+bool StructuredCloneWriteCallback(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj,
+ bool* aSameProcessRequired, void* aClosure) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aClosure);
+
+ auto* const cloneWriteInfo =
+ static_cast<IDBObjectStore::StructuredCloneWriteInfo*>(aClosure);
+
+ if (JS::GetClass(aObj) == IDBObjectStore::DummyPropClass()) {
+ MOZ_ASSERT(!cloneWriteInfo->mOffsetToKeyProp);
+ cloneWriteInfo->mOffsetToKeyProp = js::GetSCOffset(aWriter);
+
+ uint64_t value = 0;
+ // Omit endian swap
+ return JS_WriteBytes(aWriter, &value, sizeof(value));
+ }
+
+ // UNWRAP_OBJECT calls might mutate this.
+ JS::Rooted<JSObject*> obj(aCx, aObj);
+
+ {
+ Blob* blob = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
+ ErrorResult rv;
+ const uint64_t nativeEndianSize = blob->GetSize(rv);
+ MOZ_ASSERT(!rv.Failed());
+
+ const uint64_t size = NativeEndian::swapToLittleEndian(nativeEndianSize);
+
+ nsString type;
+ blob->GetType(type);
+
+ const NS_ConvertUTF16toUTF8 convType(type);
+ const uint32_t convTypeLength =
+ NativeEndian::swapToLittleEndian(convType.Length());
+
+ if (cloneWriteInfo->mFiles.Length() > size_t(UINT32_MAX)) {
+ MOZ_ASSERT(false,
+ "Fix the structured clone data to use a bigger type!");
+ return false;
+ }
+
+ const uint32_t index = cloneWriteInfo->mFiles.Length();
+
+ if (!JS_WriteUint32Pair(aWriter,
+ blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
+ index) ||
+ !JS_WriteBytes(aWriter, &size, sizeof(size)) ||
+ !JS_WriteBytes(aWriter, &convTypeLength, sizeof(convTypeLength)) ||
+ !JS_WriteBytes(aWriter, convType.get(), convType.Length())) {
+ return false;
+ }
+
+ const RefPtr<File> file = blob->ToFile();
+ if (file) {
+ ErrorResult rv;
+ const int64_t nativeEndianLastModifiedDate = file->GetLastModified(rv);
+ MOZ_ALWAYS_TRUE(!rv.Failed());
+
+ const int64_t lastModifiedDate =
+ NativeEndian::swapToLittleEndian(nativeEndianLastModifiedDate);
+
+ nsString name;
+ file->GetName(name);
+
+ const NS_ConvertUTF16toUTF8 convName(name);
+ const uint32_t convNameLength =
+ NativeEndian::swapToLittleEndian(convName.Length());
+
+ if (!JS_WriteBytes(aWriter, &lastModifiedDate,
+ sizeof(lastModifiedDate)) ||
+ !JS_WriteBytes(aWriter, &convNameLength, sizeof(convNameLength)) ||
+ !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
+ return false;
+ }
+ }
+
+ cloneWriteInfo->mFiles.EmplaceBack(StructuredCloneFileBase::eBlob, blob);
+
+ return true;
+ }
+ }
+
+ return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
+ aObj);
+}
+
+bool CopyingStructuredCloneWriteCallback(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj,
+ bool* aSameProcessRequired,
+ void* aClosure) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aClosure);
+
+ auto* const cloneInfo =
+ static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
+
+ // UNWRAP_OBJECT calls might mutate this.
+ JS::Rooted<JSObject*> obj(aCx, aObj);
+
+ {
+ Blob* blob = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
+ if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
+ MOZ_ASSERT(false,
+ "Fix the structured clone data to use a bigger type!");
+ return false;
+ }
+
+ const uint32_t index = cloneInfo->mFiles.Length();
+
+ if (!JS_WriteUint32Pair(aWriter,
+ blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
+ index)) {
+ return false;
+ }
+
+ cloneInfo->mFiles.EmplaceBack(StructuredCloneFileBase::eBlob, blob);
+
+ return true;
+ }
+ }
+
+ return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
+ aObj);
+}
+
+nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) {
+ static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = {
+ nullptr /* read */, StructuredCloneWriteCallback /* write */,
+ nullptr /* reportError */, nullptr /* readTransfer */,
+ nullptr /* writeTransfer */, nullptr /* freeTransfer */,
+ nullptr /* canTransfer */, nullptr /* sabCloned */
+ };
+
+ MOZ_ASSERT(aCx);
+
+ auto* const data = static_cast<GetAddInfoClosure*>(aClosure);
+ MOZ_ASSERT(data);
+
+ data->mCloneWriteInfo.mOffsetToKeyProp = 0;
+
+ if (!data->mCloneWriteInfo.mCloneBuffer.write(aCx, data->mValue,
+ &kStructuredCloneCallbacks,
+ &data->mCloneWriteInfo)) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ return NS_OK;
+}
+
+using indexedDB::WrapAsJSObject;
+
+template <typename T>
+JSObject* WrapAsJSObject(JSContext* const aCx, T& aBaseObject) {
+ JS::Rooted<JSObject*> result(aCx);
+ const bool res = WrapAsJSObject(aCx, aBaseObject, &result);
+ return res ? static_cast<JSObject*>(result) : nullptr;
+}
+
+JSObject* CopyingStructuredCloneReadCallback(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
+ void* aClosure) {
+ MOZ_ASSERT(aTag != SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);
+
+ if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE ||
+ aTag == SCTAG_DOM_MUTABLEFILE) {
+ auto* const cloneInfo =
+ static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
+
+ if (aData >= cloneInfo->mFiles.Length()) {
+ MOZ_ASSERT(false, "Bad index value!");
+ return nullptr;
+ }
+
+ StructuredCloneFileChild& file = cloneInfo->mFiles[aData];
+
+ switch (static_cast<StructuredCloneTags>(aTag)) {
+ case SCTAG_DOM_BLOB:
+ MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eBlob);
+ MOZ_ASSERT(!file.Blob().IsFile());
+
+ return WrapAsJSObject(aCx, file.MutableBlob());
+
+ case SCTAG_DOM_FILE: {
+ MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eBlob);
+
+ JS::Rooted<JSObject*> result(aCx);
+
+ {
+ // Create a scope so ~RefPtr fires before returning an unwrapped
+ // JS::Value.
+ const RefPtr<Blob> blob = file.BlobPtr();
+ MOZ_ASSERT(blob->IsFile());
+
+ const RefPtr<File> file = blob->ToFile();
+ MOZ_ASSERT(file);
+
+ if (!WrapAsJSObject(aCx, file, &result)) {
+ return nullptr;
+ }
+ }
+
+ return result;
+ }
+
+ case SCTAG_DOM_MUTABLEFILE:
+ MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eMutableFile);
+
+ return nullptr;
+
+ default:
+ // This cannot be reached due to the if condition before.
+ break;
+ }
+ }
+
+ return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
+ aTag);
+}
+
+} // namespace
+
+const JSClass IDBObjectStore::sDummyPropJSClass = {
+ "IDBObjectStore Dummy", 0 /* flags */
+};
+
+IDBObjectStore::IDBObjectStore(SafeRefPtr<IDBTransaction> aTransaction,
+ ObjectStoreSpec* aSpec)
+ : mTransaction(std::move(aTransaction)),
+ mCachedKeyPath(JS::UndefinedValue()),
+ mSpec(aSpec),
+ mId(aSpec->metadata().id()),
+ mRooted(false) {
+ MOZ_ASSERT(mTransaction);
+ mTransaction->AssertIsOnOwningThread();
+ MOZ_ASSERT(aSpec);
+}
+
+IDBObjectStore::~IDBObjectStore() {
+ AssertIsOnOwningThread();
+
+ if (mRooted) {
+ mozilla::DropJSObjects(this);
+ }
+}
+
+// static
+RefPtr<IDBObjectStore> IDBObjectStore::Create(
+ SafeRefPtr<IDBTransaction> aTransaction, ObjectStoreSpec& aSpec) {
+ MOZ_ASSERT(aTransaction);
+ aTransaction->AssertIsOnOwningThread();
+
+ return new IDBObjectStore(std::move(aTransaction), &aSpec);
+}
+
+// static
+void IDBObjectStore::AppendIndexUpdateInfo(
+ const int64_t aIndexID, const KeyPath& aKeyPath, const bool aMultiEntry,
+ const nsCString& aLocale, JSContext* const aCx, JS::Handle<JS::Value> aVal,
+ nsTArray<IndexUpdateInfo>* const aUpdateInfoArray, ErrorResult* const aRv) {
+ // This precondition holds when `aVal` is the result of a structured clone.
+ js::AutoAssertNoContentJS noContentJS(aCx);
+
+ if (!aMultiEntry) {
+ Key key;
+ *aRv = aKeyPath.ExtractKey(aCx, aVal, key);
+
+ // If an index's keyPath doesn't match an object, we ignore that object.
+ if (aRv->ErrorCodeIs(NS_ERROR_DOM_INDEXEDDB_DATA_ERR) || key.IsUnset()) {
+ aRv->SuppressException();
+ return;
+ }
+
+ if (aRv->Failed()) {
+ return;
+ }
+
+ QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, key, aLocale),
+ QM_VOID,
+ [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
+
+ aUpdateInfoArray->AppendElement(std::move(item));
+ return;
+ }
+
+ JS::Rooted<JS::Value> val(aCx);
+ if (NS_FAILED(aKeyPath.ExtractKeyAsJSVal(aCx, aVal, val.address()))) {
+ return;
+ }
+
+ bool isArray;
+ if (NS_WARN_IF(!JS::IsArrayObject(aCx, val, &isArray))) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ return;
+ }
+ if (isArray) {
+ JS::Rooted<JSObject*> array(aCx, &val.toObject());
+ uint32_t arrayLength;
+ if (NS_WARN_IF(!JS::GetArrayLength(aCx, array, &arrayLength))) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ return;
+ }
+
+ for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
+ JS::Rooted<JS::PropertyKey> indexId(aCx);
+ if (NS_WARN_IF(!JS_IndexToId(aCx, arrayIndex, &indexId))) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ return;
+ }
+
+ bool hasOwnProperty;
+ if (NS_WARN_IF(
+ !JS_HasOwnPropertyById(aCx, array, indexId, &hasOwnProperty))) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ return;
+ }
+
+ if (!hasOwnProperty) {
+ continue;
+ }
+
+ JS::Rooted<JS::Value> arrayItem(aCx);
+ if (NS_WARN_IF(!JS_GetPropertyById(aCx, array, indexId, &arrayItem))) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ return;
+ }
+
+ Key value;
+ auto result = value.SetFromJSVal(aCx, arrayItem);
+ if (result.isErr() || value.IsUnset()) {
+ // Not a value we can do anything with, ignore it.
+ if (result.isErr() &&
+ result.inspectErr().Is(SpecialValues::Exception)) {
+ result.unwrapErr().AsException().SuppressException();
+ }
+ continue;
+ }
+
+ QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, value, aLocale),
+ QM_VOID,
+ [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
+
+ aUpdateInfoArray->AppendElement(std::move(item));
+ }
+ } else {
+ Key value;
+ auto result = value.SetFromJSVal(aCx, val);
+ if (result.isErr() || value.IsUnset()) {
+ // Not a value we can do anything with, ignore it.
+ if (result.isErr() && result.inspectErr().Is(SpecialValues::Exception)) {
+ result.unwrapErr().AsException().SuppressException();
+ }
+ return;
+ }
+
+ QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, value, aLocale),
+ QM_VOID,
+ [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
+
+ aUpdateInfoArray->AppendElement(std::move(item));
+ }
+}
+
+// static
+void IDBObjectStore::ClearCloneReadInfo(
+ StructuredCloneReadInfoChild& aReadInfo) {
+ // This is kind of tricky, we only want to release stuff on the main thread,
+ // but we can end up being called on other threads if we have already been
+ // cleared on the main thread.
+ if (!aReadInfo.HasFiles()) {
+ return;
+ }
+
+ aReadInfo.ReleaseFiles();
+}
+
+// static
+bool IDBObjectStore::DeserializeValue(
+ JSContext* aCx, StructuredCloneReadInfoChild&& aCloneReadInfo,
+ JS::MutableHandle<JS::Value> aValue) {
+ MOZ_ASSERT(aCx);
+
+ if (!aCloneReadInfo.Data().Size()) {
+ aValue.setUndefined();
+ return true;
+ }
+
+ MOZ_ASSERT(!(aCloneReadInfo.Data().Size() % sizeof(uint64_t)));
+
+ static const JSStructuredCloneCallbacks callbacks = {
+ StructuredCloneReadCallback<StructuredCloneReadInfoChild>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr};
+
+ // FIXME: Consider to use StructuredCloneHolder here and in other
+ // deserializing methods.
+ return JS_ReadStructuredClone(
+ aCx, aCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION,
+ JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
+ JS::CloneDataPolicy(), &callbacks, &aCloneReadInfo);
+}
+
+#ifdef DEBUG
+
+void IDBObjectStore::AssertIsOnOwningThread() const {
+ MOZ_ASSERT(mTransaction);
+ mTransaction->AssertIsOnOwningThread();
+}
+
+#endif // DEBUG
+
+void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper,
+ JS::Handle<JS::Value> aKeyVal,
+ StructuredCloneWriteInfo& aCloneWriteInfo,
+ Key& aKey,
+ nsTArray<IndexUpdateInfo>& aUpdateInfoArray,
+ ErrorResult& aRv) {
+ // Return DATA_ERR if a key was passed in and this objectStore uses inline
+ // keys.
+ if (!aKeyVal.isUndefined() && HasValidKeyPath()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
+ return;
+ }
+
+ const bool isAutoIncrement = AutoIncrement();
+
+ if (!HasValidKeyPath()) {
+ // Out-of-line keys must be passed in.
+ auto result = aKey.SetFromJSVal(aCx, aKeyVal);
+ if (result.isErr()) {
+ aRv = result.unwrapErr().ExtractErrorResult(
+ InvalidMapsTo<NS_ERROR_DOM_INDEXEDDB_DATA_ERR>);
+ return;
+ }
+ } else if (!isAutoIncrement) {
+ if (!aValueWrapper.Clone(aCx)) {
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+
+ aRv = GetKeyPath().ExtractKey(aCx, aValueWrapper.Value(), aKey);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Return DATA_ERR if no key was specified this isn't an autoIncrement
+ // objectStore.
+ if (aKey.IsUnset() && !isAutoIncrement) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
+ return;
+ }
+
+ // Figure out indexes and the index values to update here.
+
+ if (mSpec->indexes().Length() && !aValueWrapper.Clone(aCx)) {
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+
+ {
+ const nsTArray<IndexMetadata>& indexes = mSpec->indexes();
+ const uint32_t idxCount = indexes.Length();
+
+ aUpdateInfoArray.SetCapacity(idxCount); // Pretty good estimate
+
+ for (uint32_t idxIndex = 0; idxIndex < idxCount; idxIndex++) {
+ const IndexMetadata& metadata = indexes[idxIndex];
+
+ AppendIndexUpdateInfo(metadata.id(), metadata.keyPath(),
+ metadata.multiEntry(), metadata.locale(), aCx,
+ aValueWrapper.Value(), &aUpdateInfoArray, &aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+ }
+
+ if (isAutoIncrement && HasValidKeyPath()) {
+ if (!aValueWrapper.Clone(aCx)) {
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+
+ GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
+
+ MOZ_ASSERT(aKey.IsUnset());
+
+ aRv = GetKeyPath().ExtractOrCreateKey(aCx, aValueWrapper.Value(), aKey,
+ &GetAddInfoCallback, &data);
+ } else {
+ GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
+
+ aRv = GetAddInfoCallback(aCx, &data);
+ }
+}
+
+RefPtr<IDBRequest> IDBObjectStore::AddOrPut(JSContext* aCx,
+ ValueWrapper& aValueWrapper,
+ JS::Handle<JS::Value> aKey,
+ bool aOverwrite, bool aFromCursor,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT_IF(aFromCursor, aOverwrite);
+
+ if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
+ mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsWriteAllowed()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
+ return nullptr;
+ }
+
+ Key key;
+ StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database());
+ nsTArray<IndexUpdateInfo> updateInfos;
+
+ // According to spec https://w3c.github.io/IndexedDB/#clone-value,
+ // the transaction must be in inactive state during clone
+ mTransaction->TransitionToInactive();
+
+#ifdef DEBUG
+ const uint32_t previousPendingRequestCount{
+ mTransaction->GetPendingRequestCount()};
+#endif
+ GetAddInfo(aCx, aValueWrapper, aKey, cloneWriteInfo, key, updateInfos, aRv);
+ // Check that new requests were rejected in the Inactive state
+ // and possibly in the Finished state, if the transaction has been aborted,
+ // during the structured cloning.
+ MOZ_ASSERT(mTransaction->GetPendingRequestCount() ==
+ previousPendingRequestCount);
+
+ if (!mTransaction->IsAborted()) {
+ mTransaction->TransitionToActive();
+ } else if (!aRv.Failed()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
+ return nullptr; // It is mandatory to return right after throw
+ }
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Check the size limit of the serialized message which mainly consists of
+ // a StructuredCloneBuffer, an encoded object key, and the encoded index keys.
+ // kMaxIDBMsgOverhead covers the minor stuff not included in this calculation
+ // because the precise calculation would slow down this AddOrPut operation.
+ static const size_t kMaxIDBMsgOverhead = 1024 * 1024; // 1MB
+ const uint32_t maximalSizeFromPref =
+ IndexedDatabaseManager::MaxSerializedMsgSize();
+ MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead);
+ const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead;
+
+ const size_t indexUpdateInfoSize =
+ std::accumulate(updateInfos.cbegin(), updateInfos.cend(), 0u,
+ [](size_t old, const IndexUpdateInfo& updateInfo) {
+ return old + updateInfo.value().GetBuffer().Length() +
+ updateInfo.localizedValue().GetBuffer().Length();
+ });
+
+ const size_t messageSize = cloneWriteInfo.mCloneBuffer.data().Size() +
+ key.GetBuffer().Length() + indexUpdateInfoSize;
+
+ if (messageSize > kMaxMessageSize) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv.ThrowUnknownError(
+ nsPrintfCString("The serialized value is too large"
+ " (size=%zu bytes, max=%zu bytes).",
+ messageSize, kMaxMessageSize));
+ return nullptr;
+ }
+
+ ObjectStoreAddPutParams commonParams;
+ commonParams.objectStoreId() = Id();
+ commonParams.cloneInfo().data().data =
+ std::move(cloneWriteInfo.mCloneBuffer.data());
+ commonParams.cloneInfo().offsetToKeyProp() = cloneWriteInfo.mOffsetToKeyProp;
+ commonParams.key() = key;
+ commonParams.indexUpdateInfos() = std::move(updateInfos);
+
+ // Convert any blobs or mutable files into FileAddInfo.
+ QM_TRY_UNWRAP(
+ commonParams.fileAddInfos(),
+ TransformIntoNewArrayAbortOnErr(
+ cloneWriteInfo.mFiles,
+ [&database = *mTransaction->Database()](
+ auto& file) -> Result<FileAddInfo, nsresult> {
+ switch (file.Type()) {
+ case StructuredCloneFileBase::eBlob: {
+ MOZ_ASSERT(file.HasBlob());
+
+ PBackgroundIDBDatabaseFileChild* const fileActor =
+ database.GetOrCreateFileActorForBlob(file.MutableBlob());
+ if (NS_WARN_IF(!fileActor)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ }
+
+ return FileAddInfo{WrapNotNull(fileActor),
+ StructuredCloneFileBase::eBlob};
+ }
+
+ case StructuredCloneFileBase::eWasmBytecode:
+ case StructuredCloneFileBase::eWasmCompiled: {
+ MOZ_ASSERT(file.HasBlob());
+
+ PBackgroundIDBDatabaseFileChild* const fileActor =
+ database.GetOrCreateFileActorForBlob(file.MutableBlob());
+ if (NS_WARN_IF(!fileActor)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ }
+
+ return FileAddInfo{WrapNotNull(fileActor), file.Type()};
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ },
+ fallible),
+ nullptr, [&aRv](const nsresult result) { aRv = result; });
+
+ const auto& params =
+ aOverwrite ? RequestParams{ObjectStorePutParams(std::move(commonParams))}
+ : RequestParams{ObjectStoreAddParams(std::move(commonParams))};
+
+ auto request = GenerateRequest(aCx, this).unwrap();
+
+ if (!aFromCursor) {
+ if (aOverwrite) {
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).put(%s)",
+ "IDBObjectStore.put(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(key));
+ } else {
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).add(%s)",
+ "IDBObjectStore.add(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(key));
+ }
+ }
+
+ mTransaction->StartRequest(request, params);
+
+ mTransaction->InvalidateCursorCaches();
+
+ return request;
+}
+
+RefPtr<IDBRequest> IDBObjectStore::GetAllInternal(
+ bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aKey,
+ const Optional<uint32_t>& aLimit, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<IDBKeyRange> keyRange;
+ IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ const int64_t id = Id();
+
+ Maybe<SerializedKeyRange> optionalKeyRange;
+ if (keyRange) {
+ SerializedKeyRange serializedKeyRange;
+ keyRange->ToSerialized(serializedKeyRange);
+ optionalKeyRange.emplace(serializedKeyRange);
+ }
+
+ const uint32_t limit = aLimit.WasPassed() ? aLimit.Value() : 0;
+
+ RequestParams params;
+ if (aKeysOnly) {
+ params = ObjectStoreGetAllKeysParams(id, optionalKeyRange, limit);
+ } else {
+ params = ObjectStoreGetAllParams(id, optionalKeyRange, limit);
+ }
+
+ auto request = GenerateRequest(aCx, this).unwrap();
+
+ if (aKeysOnly) {
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s)."
+ "getAllKeys(%s, %s)",
+ "IDBObjectStore.getAllKeys(%.0s%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
+ } else {
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s)."
+ "getAll(%s, %s)",
+ "IDBObjectStore.getAll(%.0s%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
+ }
+
+ // TODO: This is necessary to preserve request ordering only. Proper
+ // sequencing of requests should be done in a more sophisticated manner that
+ // doesn't require invalidating cursor caches (Bug 1580499).
+ mTransaction->InvalidateCursorCaches();
+
+ mTransaction->StartRequest(request, params);
+
+ return request;
+}
+
+RefPtr<IDBRequest> IDBObjectStore::Add(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aKey,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ ValueWrapper valueWrapper(aCx, aValue);
+
+ return AddOrPut(aCx, valueWrapper, aKey, false, /* aFromCursor */ false, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::Put(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aKey,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ ValueWrapper valueWrapper(aCx, aValue);
+
+ return AddOrPut(aCx, valueWrapper, aKey, true, /* aFromCursor */ false, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::Delete(JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return DeleteInternal(aCx, aKey, /* aFromCursor */ false, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::Get(JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return GetInternal(/* aKeyOnly */ false, aCx, aKey, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::GetKey(JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return GetInternal(/* aKeyOnly */ true, aCx, aKey, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::Clear(JSContext* aCx, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsWriteAllowed()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
+ return nullptr;
+ }
+
+ const ObjectStoreClearParams params = {Id()};
+
+ auto request = GenerateRequest(aCx, this).unwrap();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).clear()",
+ "IDBObjectStore.clear(%.0s%.0s%.0s)", mTransaction->LoggingSerialNumber(),
+ request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this));
+
+ mTransaction->InvalidateCursorCaches();
+
+ mTransaction->StartRequest(request, params);
+
+ return request;
+}
+
+RefPtr<IDBRequest> IDBObjectStore::GetAll(JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ const Optional<uint32_t>& aLimit,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return GetAllInternal(/* aKeysOnly */ false, aCx, aKey, aLimit, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::GetAllKeys(JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ const Optional<uint32_t>& aLimit,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return GetAllInternal(/* aKeysOnly */ true, aCx, aKey, aLimit, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::OpenCursor(JSContext* aCx,
+ JS::Handle<JS::Value> aRange,
+ IDBCursorDirection aDirection,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return OpenCursorInternal(/* aKeysOnly */ false, aCx, aRange, aDirection,
+ aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::OpenCursor(JSContext* aCx,
+ IDBCursorDirection aDirection,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return OpenCursorInternal(/* aKeysOnly */ false, aCx,
+ JS::UndefinedHandleValue, aDirection, aRv);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::OpenKeyCursor(JSContext* aCx,
+ JS::Handle<JS::Value> aRange,
+ IDBCursorDirection aDirection,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ return OpenCursorInternal(/* aKeysOnly */ true, aCx, aRange, aDirection, aRv);
+}
+
+RefPtr<IDBIndex> IDBObjectStore::Index(const nsAString& aName,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mTransaction->IsCommittingOrFinished() || mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ const nsTArray<IndexMetadata>& indexMetadatas = mSpec->indexes();
+
+ const auto endIndexMetadatas = indexMetadatas.cend();
+ const auto foundMetadata =
+ std::find_if(indexMetadatas.cbegin(), endIndexMetadatas,
+ [&aName](const auto& indexMetadata) {
+ return indexMetadata.name() == aName;
+ });
+
+ if (foundMetadata == endIndexMetadatas) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ const IndexMetadata& metadata = *foundMetadata;
+
+ const auto endIndexes = mIndexes.cend();
+ const auto foundIndex =
+ std::find_if(mIndexes.cbegin(), endIndexes,
+ [desiredId = metadata.id()](const auto& index) {
+ return index->Id() == desiredId;
+ });
+
+ RefPtr<IDBIndex> index;
+
+ if (foundIndex == endIndexes) {
+ index = IDBIndex::Create(this, metadata);
+ MOZ_ASSERT(index);
+
+ mIndexes.AppendElement(index);
+ } else {
+ index = *foundIndex;
+ }
+
+ return index;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IDBObjectStore)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBObjectStore)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedKeyPath)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexes)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedIndexes)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBObjectStore)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+
+ // Don't unlink mTransaction!
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexes)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedIndexes)
+
+ tmp->mCachedKeyPath.setUndefined();
+
+ if (tmp->mRooted) {
+ mozilla::DropJSObjects(tmp);
+ tmp->mRooted = false;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBObjectStore)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBObjectStore)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBObjectStore)
+
+JSObject* IDBObjectStore::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return IDBObjectStore_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIGlobalObject* IDBObjectStore::GetParentObject() const {
+ return mTransaction->GetParentObject();
+}
+
+void IDBObjectStore::GetKeyPath(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv) {
+ if (!mCachedKeyPath.isUndefined()) {
+ aResult.set(mCachedKeyPath);
+ return;
+ }
+
+ aRv = GetKeyPath().ToJSVal(aCx, mCachedKeyPath);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (mCachedKeyPath.isGCThing()) {
+ mozilla::HoldJSObjects(this);
+ mRooted = true;
+ }
+
+ aResult.set(mCachedKeyPath);
+}
+
+RefPtr<DOMStringList> IDBObjectStore::IndexNames() {
+ AssertIsOnOwningThread();
+
+ return CreateSortedDOMStringList(
+ mSpec->indexes(), [](const auto& index) { return index.name(); });
+}
+
+RefPtr<IDBRequest> IDBObjectStore::GetInternal(bool aKeyOnly, JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<IDBKeyRange> keyRange;
+ IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!keyRange) {
+ // Must specify a key or keyRange for get().
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
+ return nullptr;
+ }
+
+ const int64_t id = Id();
+
+ SerializedKeyRange serializedKeyRange;
+ keyRange->ToSerialized(serializedKeyRange);
+
+ const auto& params =
+ aKeyOnly ? RequestParams{ObjectStoreGetKeyParams(id, serializedKeyRange)}
+ : RequestParams{ObjectStoreGetParams(id, serializedKeyRange)};
+
+ auto request = GenerateRequest(aCx, this).unwrap();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).get(%s)",
+ "IDBObjectStore.get(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(keyRange));
+
+ // TODO: This is necessary to preserve request ordering only. Proper
+ // sequencing of requests should be done in a more sophisticated manner that
+ // doesn't require invalidating cursor caches (Bug 1580499).
+ mTransaction->InvalidateCursorCaches();
+
+ mTransaction->StartRequest(request, params);
+
+ return request;
+}
+
+RefPtr<IDBRequest> IDBObjectStore::DeleteInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ bool aFromCursor,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsWriteAllowed()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<IDBKeyRange> keyRange;
+ IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
+ if (NS_WARN_IF((aRv.Failed()))) {
+ return nullptr;
+ }
+
+ if (!keyRange) {
+ // Must specify a key or keyRange for delete().
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
+ return nullptr;
+ }
+
+ ObjectStoreDeleteParams params;
+ params.objectStoreId() = Id();
+ keyRange->ToSerialized(params.keyRange());
+
+ auto request = GenerateRequest(aCx, this).unwrap();
+
+ if (!aFromCursor) {
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).delete(%s)",
+ "IDBObjectStore.delete(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(keyRange));
+ }
+
+ mTransaction->StartRequest(request, params);
+
+ mTransaction->InvalidateCursorCaches();
+
+ return request;
+}
+
+RefPtr<IDBIndex> IDBObjectStore::CreateIndex(
+ const nsAString& aName, const StringOrStringSequence& aKeyPath,
+ const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
+ mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ const auto transaction = IDBTransaction::MaybeCurrent();
+ if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ const auto& indexes = mSpec->indexes();
+ const auto end = indexes.cend();
+ const auto foundIt = std::find_if(
+ indexes.cbegin(), end,
+ [&aName](const auto& index) { return aName == index.name(); });
+ if (foundIt != end) {
+ aRv.ThrowConstraintError(nsPrintfCString(
+ "Index named '%s' already exists at index '%zu'",
+ NS_ConvertUTF16toUTF8(aName).get(), foundIt.GetIndex()));
+ return nullptr;
+ }
+
+ const auto checkValid = [](const auto& keyPath) -> Result<KeyPath, nsresult> {
+ if (!keyPath.IsValid()) {
+ return Err(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ return keyPath;
+ };
+
+ QM_INFOONLY_TRY_UNWRAP(
+ const auto maybeKeyPath,
+ ([&aKeyPath, checkValid]() -> Result<KeyPath, nsresult> {
+ if (aKeyPath.IsString()) {
+ QM_TRY_RETURN(
+ KeyPath::Parse(aKeyPath.GetAsString()).andThen(checkValid));
+ }
+
+ MOZ_ASSERT(aKeyPath.IsStringSequence());
+ if (aKeyPath.GetAsStringSequence().IsEmpty()) {
+ return Err(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ QM_TRY_RETURN(
+ KeyPath::Parse(aKeyPath.GetAsStringSequence()).andThen(checkValid));
+ })());
+ if (!maybeKeyPath) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ const auto& keyPath = maybeKeyPath.ref();
+
+ if (aOptionalParameters.mMultiEntry && keyPath.IsArray()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+
+#ifdef DEBUG
+ {
+ const auto duplicateIndexName = std::any_of(
+ mIndexes.cbegin(), mIndexes.cend(),
+ [&aName](const auto& index) { return index->Name() == aName; });
+ MOZ_ASSERT(!duplicateIndexName);
+ }
+#endif
+
+ const IndexMetadata* const oldMetadataElements =
+ indexes.IsEmpty() ? nullptr : indexes.Elements();
+
+ // With this setup we only validate the passed in locale name by the time we
+ // get to encoding Keys. Maybe we should do it here right away and error out.
+
+ // Valid locale names are always ASCII as per BCP-47.
+ nsCString locale = NS_LossyConvertUTF16toASCII(aOptionalParameters.mLocale);
+ bool autoLocale = locale.EqualsASCII("auto");
+ if (autoLocale) {
+ locale = IndexedDatabaseManager::GetLocale();
+ }
+
+ IndexMetadata* const metadata = mSpec->indexes().EmplaceBack(
+ transaction->NextIndexId(), nsString(aName), keyPath, locale,
+ aOptionalParameters.mUnique, aOptionalParameters.mMultiEntry, autoLocale);
+
+ if (oldMetadataElements && oldMetadataElements != indexes.Elements()) {
+ MOZ_ASSERT(indexes.Length() > 1);
+
+ // Array got moved, update the spec pointers for all live indexes.
+ RefreshSpec(/* aMayDelete */ false);
+ }
+
+ transaction->CreateIndex(this, *metadata);
+
+ auto index = IDBIndex::Create(this, *metadata);
+
+ mIndexes.AppendElement(index);
+
+ // Don't do this in the macro because we always need to increment the serial
+ // number to keep in sync with the parent.
+ const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).createIndex(%s)",
+ "IDBObjectStore.createIndex(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), requestSerialNumber,
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(index));
+
+ return index;
+}
+
+void IDBObjectStore::DeleteIndex(const nsAString& aName, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
+ mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ const auto transaction = IDBTransaction::MaybeCurrent();
+ if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return;
+ }
+
+ const auto& metadataArray = mSpec->indexes();
+
+ const auto endMetadata = metadataArray.cend();
+ const auto foundMetadataIt = std::find_if(
+ metadataArray.cbegin(), endMetadata,
+ [&aName](const auto& metadata) { return aName == metadata.name(); });
+
+ if (foundMetadataIt == endMetadata) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
+ return;
+ }
+
+ const auto foundId = foundMetadataIt->id();
+ MOZ_ASSERT(foundId);
+
+ // Must remove index from mIndexes before altering the metadata array!
+ {
+ const auto end = mIndexes.end();
+ const auto foundIt = std::find_if(
+ mIndexes.begin(), end,
+ [foundId](const auto& index) { return index->Id() == foundId; });
+ // TODO: Or should we assert foundIt != end?
+ if (foundIt != end) {
+ auto& index = *foundIt;
+
+ index->NoteDeletion();
+
+ mDeletedIndexes.EmplaceBack(std::move(index));
+ mIndexes.RemoveElementAt(foundIt.GetIndex());
+ }
+ }
+
+ mSpec->indexes().RemoveElementAt(foundMetadataIt.GetIndex());
+
+ RefreshSpec(/* aMayDelete */ false);
+
+ // Don't do this in the macro because we always need to increment the serial
+ // number to keep in sync with the parent.
+ const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s)."
+ "deleteIndex(\"%s\")",
+ "IDBObjectStore.deleteIndex(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), requestSerialNumber,
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ NS_ConvertUTF16toUTF8(aName).get());
+
+ transaction->DeleteIndex(this, foundId);
+}
+
+RefPtr<IDBRequest> IDBObjectStore::Count(JSContext* aCx,
+ JS::Handle<JS::Value> aKey,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<IDBKeyRange> keyRange;
+ IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ ObjectStoreCountParams params;
+ params.objectStoreId() = Id();
+
+ if (keyRange) {
+ SerializedKeyRange serializedKeyRange;
+ keyRange->ToSerialized(serializedKeyRange);
+ params.optionalKeyRange().emplace(serializedKeyRange);
+ }
+
+ auto request = GenerateRequest(aCx, this).unwrap();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).count(%s)",
+ "IDBObjectStore.count(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(keyRange));
+
+ // TODO: This is necessary to preserve request ordering only. Proper
+ // sequencing of requests should be done in a more sophisticated manner that
+ // doesn't require invalidating cursor caches (Bug 1580499).
+ mTransaction->InvalidateCursorCaches();
+
+ mTransaction->StartRequest(request, params);
+
+ return request;
+}
+
+RefPtr<IDBRequest> IDBObjectStore::OpenCursorInternal(
+ bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aRange,
+ IDBCursorDirection aDirection, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCx);
+
+ if (mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!mTransaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<IDBKeyRange> keyRange;
+ IDBKeyRange::FromJSVal(aCx, aRange, &keyRange, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ const int64_t objectStoreId = Id();
+
+ Maybe<SerializedKeyRange> optionalKeyRange;
+
+ if (keyRange) {
+ SerializedKeyRange serializedKeyRange;
+ keyRange->ToSerialized(serializedKeyRange);
+
+ optionalKeyRange.emplace(std::move(serializedKeyRange));
+ }
+
+ const CommonOpenCursorParams commonParams = {
+ objectStoreId, std::move(optionalKeyRange), aDirection};
+
+ // TODO: It would be great if the IPDL generator created a constructor
+ // accepting a CommonOpenCursorParams by value or rvalue reference.
+ const auto params =
+ aKeysOnly ? OpenCursorParams{ObjectStoreOpenKeyCursorParams{commonParams}}
+ : OpenCursorParams{ObjectStoreOpenCursorParams{commonParams}};
+
+ auto request = GenerateRequest(aCx, this).unwrap();
+
+ if (aKeysOnly) {
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s)."
+ "openKeyCursor(%s, %s)",
+ "IDBObjectStore.openKeyCursor(%.0s%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aDirection));
+ } else {
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s)."
+ "openCursor(%s, %s)",
+ "IDBObjectStore.openCursor(%.0s%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aDirection));
+ }
+
+ const auto actor =
+ aKeysOnly
+ ? static_cast<SafeRefPtr<BackgroundCursorChildBase>>(
+ MakeSafeRefPtr<
+ BackgroundCursorChild<IDBCursorType::ObjectStoreKey>>(
+ request, this, aDirection))
+ : MakeSafeRefPtr<BackgroundCursorChild<IDBCursorType::ObjectStore>>(
+ request, this, aDirection);
+
+ // TODO: This is necessary to preserve request ordering only. Proper
+ // sequencing of requests should be done in a more sophisticated manner that
+ // doesn't require invalidating cursor caches (Bug 1580499).
+ mTransaction->InvalidateCursorCaches();
+
+ mTransaction->OpenCursor(*actor, params);
+
+ return request;
+}
+
+void IDBObjectStore::RefreshSpec(bool aMayDelete) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT_IF(mDeletedSpec, mSpec == mDeletedSpec.get());
+
+ auto* const foundObjectStoreSpec =
+ mTransaction->Database()->LookupModifiableObjectStoreSpec(
+ [id = Id()](const auto& objSpec) {
+ return objSpec.metadata().id() == id;
+ });
+ if (foundObjectStoreSpec) {
+ mSpec = foundObjectStoreSpec;
+
+ for (auto& index : mIndexes) {
+ index->RefreshMetadata(aMayDelete);
+ }
+
+ for (auto& index : mDeletedIndexes) {
+ index->RefreshMetadata(false);
+ }
+ }
+
+ MOZ_ASSERT_IF(!aMayDelete && !mDeletedSpec, foundObjectStoreSpec);
+
+ if (foundObjectStoreSpec) {
+ MOZ_ASSERT(mSpec != mDeletedSpec.get());
+ mDeletedSpec = nullptr;
+ } else {
+ NoteDeletion();
+ }
+}
+
+const ObjectStoreSpec& IDBObjectStore::Spec() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return *mSpec;
+}
+
+void IDBObjectStore::NoteDeletion() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+ MOZ_ASSERT(Id() == mSpec->metadata().id());
+
+ if (mDeletedSpec) {
+ MOZ_ASSERT(mDeletedSpec.get() == mSpec);
+ return;
+ }
+
+ // Copy the spec here.
+ mDeletedSpec = MakeUnique<ObjectStoreSpec>(*mSpec);
+ mDeletedSpec->indexes().Clear();
+
+ mSpec = mDeletedSpec.get();
+
+ for (const auto& index : mIndexes) {
+ index->NoteDeletion();
+ }
+}
+
+const nsString& IDBObjectStore::Name() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return mSpec->metadata().name();
+}
+
+void IDBObjectStore::SetName(const nsAString& aName, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
+ mDeletedSpec) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ const auto transaction = IDBTransaction::MaybeCurrent();
+ if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return;
+ }
+
+ if (aName == mSpec->metadata().name()) {
+ return;
+ }
+
+ // Cache logging string of this object store before renaming.
+ const LoggingString loggingOldObjectStore(this);
+
+ const nsresult rv =
+ transaction->Database()->RenameObjectStore(mSpec->metadata().id(), aName);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ // Don't do this in the macro because we always need to increment the serial
+ // number to keep in sync with the parent.
+ const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).objectStore(%s).rename(%s)",
+ "IDBObjectStore.rename(%.0s%.0s%.0s%.0s)",
+ mTransaction->LoggingSerialNumber(), requestSerialNumber,
+ IDB_LOG_STRINGIFY(mTransaction->Database()),
+ IDB_LOG_STRINGIFY(*mTransaction), loggingOldObjectStore.get(),
+ IDB_LOG_STRINGIFY(this));
+
+ transaction->RenameObjectStore(mSpec->metadata().id(), aName);
+}
+
+bool IDBObjectStore::AutoIncrement() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return mSpec->metadata().autoIncrement();
+}
+
+const indexedDB::KeyPath& IDBObjectStore::GetKeyPath() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return mSpec->metadata().keyPath();
+}
+
+bool IDBObjectStore::HasValidKeyPath() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return GetKeyPath().IsValid();
+}
+
+bool IDBObjectStore::ValueWrapper::Clone(JSContext* aCx) {
+ if (mCloned) {
+ return true;
+ }
+
+ static const JSStructuredCloneCallbacks callbacks = {
+ CopyingStructuredCloneReadCallback /* read */,
+ CopyingStructuredCloneWriteCallback /* write */,
+ nullptr /* reportError */,
+ nullptr /* readTransfer */,
+ nullptr /* writeTransfer */,
+ nullptr /* freeTransfer */,
+ nullptr /* canTransfer */,
+ nullptr /* sabCloned */
+ };
+
+ StructuredCloneInfo cloneInfo;
+
+ JS::Rooted<JS::Value> clonedValue(aCx);
+ if (!JS_StructuredClone(aCx, mValue, &clonedValue, &callbacks, &cloneInfo)) {
+ return false;
+ }
+
+ mValue = clonedValue;
+
+ mCloned = true;
+
+ return true;
+}
+
+} // namespace mozilla::dom