summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/ActorsParentCommon.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/indexedDB/ActorsParentCommon.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/indexedDB/ActorsParentCommon.cpp')
-rw-r--r--dom/indexedDB/ActorsParentCommon.cpp739
1 files changed, 739 insertions, 0 deletions
diff --git a/dom/indexedDB/ActorsParentCommon.cpp b/dom/indexedDB/ActorsParentCommon.cpp
new file mode 100644
index 0000000000..52e35c66f5
--- /dev/null
+++ b/dom/indexedDB/ActorsParentCommon.cpp
@@ -0,0 +1,739 @@
+/* -*- 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 "ActorsParentCommon.h"
+
+// local includes
+#include "DatabaseFileInfo.h"
+#include "DatabaseFileManager.h"
+#include "IndexedDatabase.h" // for StructuredCloneFile...
+#include "IndexedDatabaseInlines.h"
+#include "IndexedDatabaseManager.h"
+#include "IndexedDBCipherKeyManager.h"
+#include "IndexedDBCommon.h"
+#include "ReportInternalError.h"
+
+// global includes
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <numeric>
+#include <type_traits>
+#include "MainThreadUtils.h"
+#include "SafeRefPtr.h"
+#include "js/RootingAPI.h"
+#include "js/StructuredClone.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageValueArray.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/JSObjectHolder.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryScalarEnums.h"
+#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/dom/quota/ScopedLogExtraInfo.h"
+#include "mozilla/fallible.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/mozalloc.h"
+#include "nsCOMPtr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIInputStream.h"
+#include "nsIPrincipal.h"
+#include "nsIXPConnect.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsXULAppAPI.h"
+#include "snappy/snappy.h"
+
+class nsIFile;
+
+namespace mozilla::dom::indexedDB {
+
+static_assert(SNAPPY_VERSION == 0x010109);
+
+using mozilla::ipc::IsOnBackgroundThread;
+
+const nsLiteralString kJournalDirectoryName = u"journals"_ns;
+
+namespace {
+
+constexpr StructuredCloneFileBase::FileType ToStructuredCloneFileType(
+ const char16_t aTag) {
+ switch (aTag) {
+ case char16_t('-'):
+ return StructuredCloneFileBase::eMutableFile;
+
+ case char16_t('.'):
+ return StructuredCloneFileBase::eStructuredClone;
+
+ case char16_t('/'):
+ return StructuredCloneFileBase::eWasmBytecode;
+
+ case char16_t('\\'):
+ return StructuredCloneFileBase::eWasmCompiled;
+
+ default:
+ return StructuredCloneFileBase::eBlob;
+ }
+}
+
+int32_t ToInteger(const nsAString& aStr, nsresult* const aRv) {
+ return aStr.ToInteger(aRv);
+}
+
+Result<StructuredCloneFileParent, nsresult> DeserializeStructuredCloneFile(
+ const DatabaseFileManager& aFileManager,
+ const nsDependentSubstring& aText) {
+ MOZ_ASSERT(!aText.IsEmpty());
+
+ const StructuredCloneFileBase::FileType type =
+ ToStructuredCloneFileType(aText.First());
+
+ QM_TRY_INSPECT(const auto& id,
+ MOZ_TO_RESULT_GET_TYPED(
+ int32_t, ToInteger,
+ type == StructuredCloneFileBase::eBlob
+ ? aText
+ : static_cast<const nsAString&>(Substring(aText, 1))));
+
+ SafeRefPtr<DatabaseFileInfo> fileInfo = aFileManager.GetFileInfo(id);
+ MOZ_ASSERT(fileInfo);
+ // XXX In bug 1432133, for some reasons DatabaseFileInfo object cannot be
+ // got. This is just a short-term fix, and we are working on finding the real
+ // cause in bug 1519859.
+ QM_TRY(OkIf((bool)fileInfo), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
+
+ return StructuredCloneFileParent{type, std::move(fileInfo)};
+}
+
+// This class helps to create only 1 sandbox.
+class SandboxHolder final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(SandboxHolder)
+
+ private:
+ friend JSObject* mozilla::dom::indexedDB::GetSandbox(JSContext* aCx);
+
+ ~SandboxHolder() = default;
+
+ static SandboxHolder* GetOrCreate() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ static StaticRefPtr<SandboxHolder> sHolder;
+ if (!sHolder) {
+ sHolder = new SandboxHolder();
+ ClearOnShutdown(&sHolder);
+ }
+ return sHolder;
+ }
+
+ JSObject* GetSandboxInternal(JSContext* aCx) {
+ if (!mSandbox) {
+ nsIXPConnect* const xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+
+ // Let's use a null principal.
+ const nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+
+ JS::Rooted<JSObject*> sandbox(aCx);
+ QM_TRY(
+ MOZ_TO_RESULT(xpc->CreateSandbox(aCx, principal, sandbox.address())),
+ nullptr);
+
+ mSandbox = new JSObjectHolder(aCx, sandbox);
+ }
+
+ return mSandbox->GetJSObject();
+ }
+
+ RefPtr<JSObjectHolder> mSandbox;
+};
+
+uint32_t CompressedByteCountForNumber(uint64_t aNumber) {
+ // All bytes have 7 bits available.
+ uint32_t count = 1;
+ while ((aNumber >>= 7)) {
+ count++;
+ }
+
+ return count;
+}
+
+uint32_t CompressedByteCountForIndexId(IndexOrObjectStoreId aIndexId) {
+ MOZ_ASSERT(aIndexId);
+ MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
+ "Overflow!");
+
+ return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
+}
+
+void WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator) {
+ MOZ_ASSERT(aIterator);
+ MOZ_ASSERT(*aIterator);
+
+ uint8_t*& buffer = *aIterator;
+
+#ifdef DEBUG
+ const uint8_t* const bufferStart = buffer;
+ const uint64_t originalNumber = aNumber;
+#endif
+
+ while (true) {
+ uint64_t shiftedNumber = aNumber >> 7;
+ if (shiftedNumber) {
+ *buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
+ aNumber = shiftedNumber;
+ } else {
+ *buffer++ = uint8_t(aNumber);
+ break;
+ }
+ }
+
+ MOZ_ASSERT(buffer > bufferStart);
+ MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
+ CompressedByteCountForNumber(originalNumber));
+}
+
+void WriteCompressedIndexId(IndexOrObjectStoreId aIndexId, bool aUnique,
+ uint8_t** aIterator) {
+ MOZ_ASSERT(aIndexId);
+ MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
+ "Overflow!");
+ MOZ_ASSERT(aIterator);
+ MOZ_ASSERT(*aIterator);
+
+ const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
+ WriteCompressedNumber(indexId, aIterator);
+}
+
+// aOutIndexValues is an output parameter, since its storage is reused.
+nsresult ReadCompressedIndexDataValuesFromBlob(
+ const Span<const uint8_t> aBlobData,
+ nsTArray<IndexDataValue>* aOutIndexValues) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(!aBlobData.IsEmpty());
+ MOZ_ASSERT(aOutIndexValues);
+ MOZ_ASSERT(aOutIndexValues->IsEmpty());
+
+ AUTO_PROFILER_LABEL("ReadCompressedIndexDataValuesFromBlob", DOM);
+
+ // XXX Is this check still necessary with a Span? Or should it rather be moved
+ // to the caller?
+ QM_TRY(OkIf(uintptr_t(aBlobData.Elements()) <=
+ UINTPTR_MAX - aBlobData.LengthBytes()),
+ NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ for (auto remainder = aBlobData; !remainder.IsEmpty();) {
+ QM_TRY_INSPECT((const auto& [indexId, unique, remainderAfterIndexId]),
+ ReadCompressedIndexId(remainder));
+
+ QM_TRY(OkIf(!remainderAfterIndexId.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
+ IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ // Read key buffer length.
+ QM_TRY_INSPECT(
+ (const auto& [keyBufferLength, remainderAfterKeyBufferLength]),
+ ReadCompressedNumber(remainderAfterIndexId));
+
+ QM_TRY(OkIf(!remainderAfterKeyBufferLength.IsEmpty()),
+ NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ QM_TRY(OkIf(keyBufferLength <= uint64_t(UINT32_MAX)),
+ NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ QM_TRY(OkIf(keyBufferLength <= remainderAfterKeyBufferLength.Length()),
+ NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ const auto [keyBuffer, remainderAfterKeyBuffer] =
+ remainderAfterKeyBufferLength.SplitAt(keyBufferLength);
+ auto idv =
+ IndexDataValue{indexId, unique, Key{nsCString{AsChars(keyBuffer)}}};
+
+ // Read sort key buffer length.
+ QM_TRY_INSPECT(
+ (const auto& [sortKeyBufferLength, remainderAfterSortKeyBufferLength]),
+ ReadCompressedNumber(remainderAfterKeyBuffer));
+
+ remainder = remainderAfterSortKeyBufferLength;
+ if (sortKeyBufferLength > 0) {
+ QM_TRY(OkIf(!remainder.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
+ IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ QM_TRY(OkIf(sortKeyBufferLength <= uint64_t(UINT32_MAX)),
+ NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ QM_TRY(OkIf(sortKeyBufferLength <= remainder.Length()),
+ NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ const auto [sortKeyBuffer, remainderAfterSortKeyBuffer] =
+ remainder.SplitAt(sortKeyBufferLength);
+ idv.mLocaleAwarePosition = Key{nsCString{AsChars(sortKeyBuffer)}};
+ remainder = remainderAfterSortKeyBuffer;
+ }
+
+ QM_TRY(OkIf(aOutIndexValues->AppendElement(std::move(idv), fallible)),
+ NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA);
+ }
+ aOutIndexValues->Sort();
+
+ return NS_OK;
+}
+
+// aOutIndexValues is an output parameter, since its storage is reused.
+template <typename T>
+nsresult ReadCompressedIndexDataValuesFromSource(
+ T& aSource, uint32_t aColumnIndex,
+ nsTArray<IndexDataValue>* aOutIndexValues) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aOutIndexValues);
+ MOZ_ASSERT(aOutIndexValues->IsEmpty());
+
+ QM_TRY_INSPECT(
+ const int32_t& columnType,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aColumnIndex));
+
+ switch (columnType) {
+ case mozIStorageStatement::VALUE_TYPE_NULL:
+ return NS_OK;
+
+ case mozIStorageStatement::VALUE_TYPE_BLOB: {
+ // XXX ToResultInvoke does not support multiple output parameters yet, so
+ // we also can't use QM_TRY_UNWRAP/QM_TRY_INSPECT here.
+ const uint8_t* blobData;
+ uint32_t blobDataLength;
+ QM_TRY(MOZ_TO_RESULT(
+ aSource.GetSharedBlob(aColumnIndex, &blobDataLength, &blobData)));
+
+ QM_TRY(OkIf(blobDataLength), NS_ERROR_FILE_CORRUPTED,
+ IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ QM_TRY(MOZ_TO_RESULT(ReadCompressedIndexDataValuesFromBlob(
+ Span(blobData, blobDataLength), aOutIndexValues)));
+
+ return NS_OK;
+ }
+
+ default:
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+}
+
+Result<StructuredCloneReadInfoParent, nsresult>
+GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
+ uint32_t aBlobDataLength,
+ const DatabaseFileManager& aFileManager,
+ const nsAString& aFileIds) {
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromBlob", DOM);
+
+ const char* const compressed = reinterpret_cast<const char*>(aBlobData);
+ const size_t compressedLength = size_t(aBlobDataLength);
+
+ size_t uncompressedLength;
+ QM_TRY(OkIf(snappy::GetUncompressedLength(compressed, compressedLength,
+ &uncompressedLength)),
+ Err(NS_ERROR_FILE_CORRUPTED));
+
+ // `data` (JSStructuredCloneData) currently uses 4k buffer internally.
+ // For performance reasons, it's better to align `uncompressed` with that.
+ AutoTArray<uint8_t, 4096> uncompressed;
+ QM_TRY(OkIf(uncompressed.SetLength(uncompressedLength, fallible)),
+ Err(NS_ERROR_OUT_OF_MEMORY));
+
+ char* const uncompressedBuffer =
+ reinterpret_cast<char*>(uncompressed.Elements());
+
+ QM_TRY(OkIf(snappy::RawUncompress(compressed, compressedLength,
+ uncompressedBuffer)),
+ Err(NS_ERROR_FILE_CORRUPTED));
+
+ JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess);
+ QM_TRY(OkIf(data.AppendBytes(uncompressedBuffer, uncompressed.Length())),
+ Err(NS_ERROR_OUT_OF_MEMORY));
+
+ nsTArray<StructuredCloneFileParent> files;
+ if (!aFileIds.IsVoid()) {
+ QM_TRY_UNWRAP(files,
+ DeserializeStructuredCloneFiles(aFileManager, aFileIds));
+ }
+
+ return StructuredCloneReadInfoParent{std::move(data), std::move(files),
+ false};
+}
+
+Result<StructuredCloneReadInfoParent, nsresult>
+GetStructuredCloneReadInfoFromExternalBlob(
+ uint64_t aIntData, const DatabaseFileManager& aFileManager,
+ const nsAString& aFileIds) {
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromExternalBlob", DOM);
+
+ nsTArray<StructuredCloneFileParent> files;
+ if (!aFileIds.IsVoid()) {
+ QM_TRY_UNWRAP(files,
+ DeserializeStructuredCloneFiles(aFileManager, aFileIds));
+ }
+
+ // Higher and lower 32 bits described
+ // in ObjectStoreAddOrPutRequestOp::DoDatabaseWork.
+ const uint32_t index = uint32_t(aIntData & UINT32_MAX);
+
+ QM_TRY(OkIf(index < files.Length()), Err(NS_ERROR_UNEXPECTED),
+ [](const auto&) { MOZ_ASSERT(false, "Bad index value!"); });
+
+ if (StaticPrefs::dom_indexedDB_preprocessing()) {
+ return StructuredCloneReadInfoParent{
+ JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess},
+ std::move(files), true};
+ }
+
+ // XXX Why can there be multiple files, but we use only a single one here?
+ const StructuredCloneFileParent& file = files[index];
+ MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eStructuredClone);
+
+ Maybe<CipherKey> maybeKey;
+
+ if (aFileManager.IsInPrivateBrowsingMode()) {
+ nsCString fileKeyId;
+ fileKeyId.AppendInt(file.FileInfo().Id());
+
+ maybeKey = aFileManager.MutableCipherKeyManagerRef().Get(fileKeyId);
+ }
+
+ auto data = JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess};
+
+ {
+ const nsCOMPtr<nsIFile> nativeFile = file.FileInfo().GetFileForFileInfo();
+ QM_TRY(OkIf(nativeFile), Err(NS_ERROR_FAILURE));
+
+ QM_TRY_INSPECT(
+ const auto& fileInputStream,
+ NS_NewLocalFileInputStream(nativeFile)
+ .andThen([maybeKey](auto fileInputStream)
+ -> Result<nsCOMPtr<nsIInputStream>, nsresult> {
+ if (maybeKey) {
+ return nsCOMPtr<nsIInputStream>{MakeRefPtr<
+ quota::DecryptingInputStream<IndexedDBCipherStrategy>>(
+ WrapNotNull(std::move(fileInputStream)),
+ kEncryptedStreamBlockSize, *maybeKey)};
+ }
+
+ return fileInputStream;
+ }));
+
+ QM_TRY(MOZ_TO_RESULT(
+ SnappyUncompressStructuredCloneData(*fileInputStream, data)));
+ }
+
+ return StructuredCloneReadInfoParent{std::move(data), std::move(files),
+ false};
+}
+
+template <typename T>
+Result<StructuredCloneReadInfoParent, nsresult>
+GetStructuredCloneReadInfoFromSource(T* aSource, uint32_t aDataIndex,
+ uint32_t aFileIdsIndex,
+ const DatabaseFileManager& aFileManager) {
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aSource);
+
+ QM_TRY_INSPECT(
+ const int32_t& columnType,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aDataIndex));
+
+ QM_TRY_INSPECT(const bool& isNull, MOZ_TO_RESULT_INVOKE_MEMBER(
+ aSource, GetIsNull, aFileIdsIndex));
+
+ QM_TRY_INSPECT(const nsString& fileIds, ([aSource, aFileIdsIndex, isNull] {
+ return isNull ? Result<nsString, nsresult>{VoidString()}
+ : MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsString, aSource, GetString,
+ aFileIdsIndex);
+ }()));
+
+ switch (columnType) {
+ case mozIStorageStatement::VALUE_TYPE_INTEGER: {
+ QM_TRY_INSPECT(
+ const int64_t& intData,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetInt64, aDataIndex));
+
+ uint64_t uintData;
+ memcpy(&uintData, &intData, sizeof(uint64_t));
+
+ return GetStructuredCloneReadInfoFromExternalBlob(uintData, aFileManager,
+ fileIds);
+ }
+
+ case mozIStorageStatement::VALUE_TYPE_BLOB: {
+ const uint8_t* blobData;
+ uint32_t blobDataLength;
+ QM_TRY(MOZ_TO_RESULT(
+ aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData)));
+
+ return GetStructuredCloneReadInfoFromBlob(blobData, blobDataLength,
+ aFileManager, fileIds);
+ }
+
+ default:
+ return Err(NS_ERROR_FILE_CORRUPTED);
+ }
+}
+
+} // namespace
+
+IndexDataValue::IndexDataValue() : mIndexId(0), mUnique(false) {
+ MOZ_COUNT_CTOR(IndexDataValue);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+IndexDataValue::IndexDataValue(IndexDataValue&& aOther)
+ : mIndexId(aOther.mIndexId),
+ mPosition(std::move(aOther.mPosition)),
+ mLocaleAwarePosition(std::move(aOther.mLocaleAwarePosition)),
+ mUnique(aOther.mUnique) {
+ MOZ_ASSERT(!aOther.mPosition.IsUnset());
+
+ MOZ_COUNT_CTOR(IndexDataValue);
+}
+#endif
+
+IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
+ const Key& aPosition)
+ : mIndexId(aIndexId), mPosition(aPosition), mUnique(aUnique) {
+ MOZ_ASSERT(!aPosition.IsUnset());
+
+ MOZ_COUNT_CTOR(IndexDataValue);
+}
+
+IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
+ const Key& aPosition,
+ const Key& aLocaleAwarePosition)
+ : mIndexId(aIndexId),
+ mPosition(aPosition),
+ mLocaleAwarePosition(aLocaleAwarePosition),
+ mUnique(aUnique) {
+ MOZ_ASSERT(!aPosition.IsUnset());
+
+ MOZ_COUNT_CTOR(IndexDataValue);
+}
+
+bool IndexDataValue::operator==(const IndexDataValue& aOther) const {
+ if (mIndexId != aOther.mIndexId) {
+ return false;
+ }
+ if (mLocaleAwarePosition.IsUnset()) {
+ return mPosition == aOther.mPosition;
+ }
+ return mLocaleAwarePosition == aOther.mLocaleAwarePosition;
+}
+
+bool IndexDataValue::operator<(const IndexDataValue& aOther) const {
+ if (mIndexId == aOther.mIndexId) {
+ if (mLocaleAwarePosition.IsUnset()) {
+ return mPosition < aOther.mPosition;
+ }
+ return mLocaleAwarePosition < aOther.mLocaleAwarePosition;
+ }
+
+ return mIndexId < aOther.mIndexId;
+}
+
+JSObject* GetSandbox(JSContext* aCx) {
+ SandboxHolder* holder = SandboxHolder::GetOrCreate();
+ return holder->GetSandboxInternal(aCx);
+}
+
+Result<std::pair<UniqueFreePtr<uint8_t>, uint32_t>, nsresult>
+MakeCompressedIndexDataValues(const nsTArray<IndexDataValue>& aIndexValues) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ AUTO_PROFILER_LABEL("MakeCompressedIndexDataValues", DOM);
+
+ const uint32_t arrayLength = aIndexValues.Length();
+ if (!arrayLength) {
+ return std::pair{UniqueFreePtr<uint8_t>{}, 0u};
+ }
+
+ // First calculate the size of the final buffer.
+ const auto blobDataLength = std::accumulate(
+ aIndexValues.cbegin(), aIndexValues.cend(), CheckedUint32(0),
+ [](CheckedUint32 sum, const IndexDataValue& info) {
+ const nsCString& keyBuffer = info.mPosition.GetBuffer();
+ const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
+ const uint32_t keyBufferLength = keyBuffer.Length();
+ const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
+
+ MOZ_ASSERT(!keyBuffer.IsEmpty());
+
+ return sum + CompressedByteCountForIndexId(info.mIndexId) +
+ CompressedByteCountForNumber(keyBufferLength) +
+ CompressedByteCountForNumber(sortKeyBufferLength) +
+ keyBufferLength + sortKeyBufferLength;
+ });
+
+ QM_TRY(OkIf(blobDataLength.isValid()),
+ Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
+ IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ UniqueFreePtr<uint8_t> blobData(
+ static_cast<uint8_t*>(malloc(blobDataLength.value())));
+ QM_TRY(OkIf(static_cast<bool>(blobData)), Err(NS_ERROR_OUT_OF_MEMORY),
+ IDB_REPORT_INTERNAL_ERR_LAMBDA);
+
+ uint8_t* blobDataIter = blobData.get();
+
+ for (const IndexDataValue& info : aIndexValues) {
+ const nsCString& keyBuffer = info.mPosition.GetBuffer();
+ const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
+ const uint32_t keyBufferLength = keyBuffer.Length();
+ const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
+
+ WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
+ WriteCompressedNumber(keyBufferLength, &blobDataIter);
+
+ memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
+ blobDataIter += keyBufferLength;
+
+ WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
+
+ memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
+ blobDataIter += sortKeyBufferLength;
+ }
+
+ MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength.value());
+
+ return std::pair{std::move(blobData), blobDataLength.value()};
+}
+
+nsresult ReadCompressedIndexDataValues(
+ mozIStorageStatement& aStatement, uint32_t aColumnIndex,
+ nsTArray<IndexDataValue>& aOutIndexValues) {
+ return ReadCompressedIndexDataValuesFromSource(aStatement, aColumnIndex,
+ &aOutIndexValues);
+}
+
+template <typename T>
+Result<IndexDataValuesAutoArray, nsresult> ReadCompressedIndexDataValues(
+ T& aValues, uint32_t aColumnIndex) {
+ return MOZ_TO_RESULT_INVOKE_TYPED(IndexDataValuesAutoArray,
+ &ReadCompressedIndexDataValuesFromSource<T>,
+ aValues, aColumnIndex);
+}
+
+template Result<IndexDataValuesAutoArray, nsresult>
+ReadCompressedIndexDataValues<mozIStorageValueArray>(mozIStorageValueArray&,
+ uint32_t);
+
+template Result<IndexDataValuesAutoArray, nsresult>
+ReadCompressedIndexDataValues<mozIStorageStatement>(mozIStorageStatement&,
+ uint32_t);
+
+Result<std::tuple<IndexOrObjectStoreId, bool, Span<const uint8_t>>, nsresult>
+ReadCompressedIndexId(const Span<const uint8_t> aData) {
+ QM_TRY_INSPECT((const auto& [indexId, remainder]),
+ ReadCompressedNumber(aData));
+
+ MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
+
+ return std::tuple{IndexOrObjectStoreId(indexId >> 1), indexId % 2 == 1,
+ remainder};
+}
+
+Result<std::pair<uint64_t, mozilla::Span<const uint8_t>>, nsresult>
+ReadCompressedNumber(const Span<const uint8_t> aSpan) {
+ uint8_t shiftCounter = 0;
+ uint64_t result = 0;
+
+ const auto end = aSpan.cend();
+
+ const auto newPos =
+ std::find_if(aSpan.cbegin(), end, [&result, &shiftCounter](uint8_t byte) {
+ MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
+
+ result += (uint64_t(byte & 0x7f) << shiftCounter);
+ shiftCounter += 7;
+
+ return !(byte & 0x80);
+ });
+
+ QM_TRY(OkIf(newPos != end), Err(NS_ERROR_FILE_CORRUPTED), [](const auto&) {
+ MOZ_ASSERT(false);
+ IDB_REPORT_INTERNAL_ERR();
+ });
+
+ return std::pair{result, Span{newPos + 1, end}};
+}
+
+Result<StructuredCloneReadInfoParent, nsresult>
+GetStructuredCloneReadInfoFromValueArray(
+ mozIStorageValueArray* aValues, uint32_t aDataIndex, uint32_t aFileIdsIndex,
+ const DatabaseFileManager& aFileManager) {
+ return GetStructuredCloneReadInfoFromSource(aValues, aDataIndex,
+ aFileIdsIndex, aFileManager);
+}
+
+Result<StructuredCloneReadInfoParent, nsresult>
+GetStructuredCloneReadInfoFromStatement(
+ mozIStorageStatement* aStatement, uint32_t aDataIndex,
+ uint32_t aFileIdsIndex, const DatabaseFileManager& aFileManager) {
+ return GetStructuredCloneReadInfoFromSource(aStatement, aDataIndex,
+ aFileIdsIndex, aFileManager);
+}
+
+Result<nsTArray<StructuredCloneFileParent>, nsresult>
+DeserializeStructuredCloneFiles(const DatabaseFileManager& aFileManager,
+ const nsAString& aText) {
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ nsTArray<StructuredCloneFileParent> result;
+ for (const auto& token :
+ nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>(aText, ' ')
+ .ToRange()) {
+ MOZ_ASSERT(!token.IsEmpty());
+
+ QM_TRY_UNWRAP(auto structuredCloneFile,
+ DeserializeStructuredCloneFile(aFileManager, token));
+
+ result.EmplaceBack(std::move(structuredCloneFile));
+ }
+
+ return result;
+}
+
+nsresult ExecuteSimpleSQLSequence(mozIStorageConnection& aConnection,
+ Span<const nsLiteralCString> aSQLCommands) {
+ for (const auto& aSQLCommand : aSQLCommands) {
+ const auto extraInfo = quota::ScopedLogExtraInfo{
+ quota::ScopedLogExtraInfo::kTagQuery, aSQLCommand};
+
+ QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(aSQLCommand)));
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom::indexedDB