tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ActorsParentCommon.cpp (25097B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ActorsParentCommon.h"
      8 
      9 // local includes
     10 #include "DatabaseFileInfo.h"
     11 #include "DatabaseFileManager.h"
     12 #include "IndexedDBCipherKeyManager.h"
     13 #include "IndexedDBCommon.h"
     14 #include "IndexedDatabase.h"  // for StructuredCloneFile...
     15 #include "IndexedDatabaseInlines.h"
     16 #include "IndexedDatabaseManager.h"
     17 #include "ReportInternalError.h"
     18 
     19 // global includes
     20 #include <stdlib.h>
     21 #include <string.h>
     22 
     23 #include <algorithm>
     24 #include <numeric>
     25 
     26 #include "MainThreadUtils.h"
     27 #include "SafeRefPtr.h"
     28 #include "js/RootingAPI.h"
     29 #include "js/StructuredClone.h"
     30 #include "mozIStorageConnection.h"
     31 #include "mozIStorageStatement.h"
     32 #include "mozIStorageValueArray.h"
     33 #include "mozilla/Assertions.h"
     34 #include "mozilla/CheckedInt.h"
     35 #include "mozilla/ClearOnShutdown.h"
     36 #include "mozilla/JSObjectHolder.h"
     37 #include "mozilla/NullPrincipal.h"
     38 #include "mozilla/ProfilerLabels.h"
     39 #include "mozilla/RefPtr.h"
     40 #include "mozilla/ResultExtensions.h"
     41 #include "mozilla/StaticPrefs_dom.h"
     42 #include "mozilla/StaticPtr.h"
     43 #include "mozilla/dom/quota/DecryptingInputStream_impl.h"
     44 #include "mozilla/dom/quota/QuotaCommon.h"
     45 #include "mozilla/dom/quota/ResultExtensions.h"
     46 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
     47 #include "mozilla/fallible.h"
     48 #include "mozilla/ipc/BackgroundParent.h"
     49 #include "mozilla/mozalloc.h"
     50 #include "nsCOMPtr.h"
     51 #include "nsCharSeparatedTokenizer.h"
     52 #include "nsContentUtils.h"
     53 #include "nsDebug.h"
     54 #include "nsError.h"
     55 #include "nsIInputStream.h"
     56 #include "nsIPrincipal.h"
     57 #include "nsIXPConnect.h"
     58 #include "nsNetUtil.h"
     59 #include "nsString.h"
     60 #include "nsStringFlags.h"
     61 #include "nsXULAppAPI.h"
     62 #include "snappy/snappy.h"
     63 
     64 class nsIFile;
     65 
     66 namespace mozilla::dom::indexedDB {
     67 
     68 static_assert(SNAPPY_VERSION == 0x010202);
     69 
     70 using mozilla::ipc::IsOnBackgroundThread;
     71 
     72 const nsLiteralString kJournalDirectoryName = u"journals"_ns;
     73 
     74 namespace {
     75 
     76 constexpr StructuredCloneFileBase::FileType ToStructuredCloneFileType(
     77    const char16_t aTag) {
     78  switch (aTag) {
     79    case char16_t('-'):
     80      return StructuredCloneFileBase::eMutableFile;
     81 
     82    case char16_t('.'):
     83      return StructuredCloneFileBase::eStructuredClone;
     84 
     85    case char16_t('/'):
     86      return StructuredCloneFileBase::eWasmBytecode;
     87 
     88    case char16_t('\\'):
     89      return StructuredCloneFileBase::eWasmCompiled;
     90 
     91    default:
     92      return StructuredCloneFileBase::eBlob;
     93  }
     94 }
     95 
     96 int32_t ToInteger(const nsAString& aStr, nsresult* const aRv) {
     97  return aStr.ToInteger(aRv);
     98 }
     99 
    100 Result<StructuredCloneFileParent, nsresult> DeserializeStructuredCloneFile(
    101    const DatabaseFileManager& aFileManager,
    102    const nsDependentSubstring& aText) {
    103  MOZ_ASSERT(!aText.IsEmpty());
    104 
    105  const StructuredCloneFileBase::FileType type =
    106      ToStructuredCloneFileType(aText.First());
    107 
    108  QM_TRY_INSPECT(const auto& id,
    109                 MOZ_TO_RESULT_GET_TYPED(
    110                     int32_t, ToInteger,
    111                     type == StructuredCloneFileBase::eBlob
    112                         ? aText
    113                         : static_cast<const nsAString&>(Substring(aText, 1))));
    114 
    115  SafeRefPtr<DatabaseFileInfo> fileInfo = aFileManager.GetFileInfo(id);
    116  MOZ_ASSERT(fileInfo);
    117  // XXX In bug 1432133, for some reasons DatabaseFileInfo object cannot be
    118  // got. This is just a short-term fix, and we are working on finding the real
    119  // cause in bug 1519859.
    120  QM_TRY(OkIf((bool)fileInfo), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
    121 
    122  return StructuredCloneFileParent{type, std::move(fileInfo)};
    123 }
    124 
    125 // This class helps to create only 1 sandbox.
    126 class SandboxHolder final {
    127 public:
    128  NS_INLINE_DECL_REFCOUNTING(SandboxHolder)
    129 
    130 private:
    131  friend JSObject* mozilla::dom::indexedDB::GetSandbox(JSContext* aCx);
    132 
    133  ~SandboxHolder() = default;
    134 
    135  static SandboxHolder* GetOrCreate() {
    136    MOZ_ASSERT(XRE_IsParentProcess());
    137    MOZ_ASSERT(NS_IsMainThread());
    138 
    139    static StaticRefPtr<SandboxHolder> sHolder;
    140    if (!sHolder) {
    141      sHolder = new SandboxHolder();
    142      ClearOnShutdown(&sHolder);
    143    }
    144    return sHolder;
    145  }
    146 
    147  JSObject* GetSandboxInternal(JSContext* aCx) {
    148    if (!mSandbox) {
    149      nsIXPConnect* const xpc = nsContentUtils::XPConnect();
    150      MOZ_ASSERT(xpc, "This should never be null!");
    151 
    152      // Let's use a null principal.
    153      const nsCOMPtr<nsIPrincipal> principal =
    154          NullPrincipal::CreateWithoutOriginAttributes();
    155 
    156      JS::Rooted<JSObject*> sandbox(aCx);
    157      QM_TRY(
    158          MOZ_TO_RESULT(xpc->CreateSandbox(aCx, principal, sandbox.address())),
    159          nullptr);
    160 
    161      mSandbox = new JSObjectHolder(aCx, sandbox);
    162    }
    163 
    164    return mSandbox->GetJSObject();
    165  }
    166 
    167  RefPtr<JSObjectHolder> mSandbox;
    168 };
    169 
    170 uint32_t CompressedByteCountForNumber(uint64_t aNumber) {
    171  // All bytes have 7 bits available.
    172  uint32_t count = 1;
    173  while ((aNumber >>= 7)) {
    174    count++;
    175  }
    176 
    177  return count;
    178 }
    179 
    180 uint32_t CompressedByteCountForIndexId(IndexOrObjectStoreId aIndexId) {
    181  MOZ_ASSERT(aIndexId);
    182  MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
    183             "Overflow!");
    184 
    185  return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
    186 }
    187 
    188 void WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator) {
    189  MOZ_ASSERT(aIterator);
    190  MOZ_ASSERT(*aIterator);
    191 
    192  uint8_t*& buffer = *aIterator;
    193 
    194 #ifdef DEBUG
    195  const uint8_t* const bufferStart = buffer;
    196  const uint64_t originalNumber = aNumber;
    197 #endif
    198 
    199  while (true) {
    200    uint64_t shiftedNumber = aNumber >> 7;
    201    if (shiftedNumber) {
    202      *buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
    203      aNumber = shiftedNumber;
    204    } else {
    205      *buffer++ = uint8_t(aNumber);
    206      break;
    207    }
    208  }
    209 
    210  MOZ_ASSERT(buffer > bufferStart);
    211  MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
    212             CompressedByteCountForNumber(originalNumber));
    213 }
    214 
    215 void WriteCompressedIndexId(IndexOrObjectStoreId aIndexId, bool aUnique,
    216                            uint8_t** aIterator) {
    217  MOZ_ASSERT(aIndexId);
    218  MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
    219             "Overflow!");
    220  MOZ_ASSERT(aIterator);
    221  MOZ_ASSERT(*aIterator);
    222 
    223  const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
    224  WriteCompressedNumber(indexId, aIterator);
    225 }
    226 
    227 // aOutIndexValues is an output parameter, since its storage is reused.
    228 nsresult ReadCompressedIndexDataValuesFromBlob(
    229    const Span<const uint8_t> aBlobData,
    230    nsTArray<IndexDataValue>* aOutIndexValues) {
    231  MOZ_ASSERT(!NS_IsMainThread());
    232  MOZ_ASSERT(!IsOnBackgroundThread());
    233  MOZ_ASSERT(!aBlobData.IsEmpty());
    234  MOZ_ASSERT(aOutIndexValues);
    235  MOZ_ASSERT(aOutIndexValues->IsEmpty());
    236 
    237  AUTO_PROFILER_LABEL("ReadCompressedIndexDataValuesFromBlob", DOM);
    238 
    239  // XXX Is this check still necessary with a Span? Or should it rather be moved
    240  // to the caller?
    241  QM_TRY(OkIf(uintptr_t(aBlobData.Elements()) <=
    242              UINTPTR_MAX - aBlobData.LengthBytes()),
    243         NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
    244 
    245  for (auto remainder = aBlobData; !remainder.IsEmpty();) {
    246    QM_TRY_INSPECT((const auto& [indexId, unique, remainderAfterIndexId]),
    247                   ReadCompressedIndexId(remainder));
    248 
    249    QM_TRY(OkIf(!remainderAfterIndexId.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
    250           IDB_REPORT_INTERNAL_ERR_LAMBDA);
    251 
    252    // Read key buffer length.
    253    QM_TRY_INSPECT(
    254        (const auto& [keyBufferLength, remainderAfterKeyBufferLength]),
    255        ReadCompressedNumber(remainderAfterIndexId));
    256 
    257    QM_TRY(OkIf(!remainderAfterKeyBufferLength.IsEmpty()),
    258           NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
    259 
    260    QM_TRY(OkIf(keyBufferLength <= uint64_t(UINT32_MAX)),
    261           NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
    262 
    263    QM_TRY(OkIf(keyBufferLength <= remainderAfterKeyBufferLength.Length()),
    264           NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
    265 
    266    const auto [keyBuffer, remainderAfterKeyBuffer] =
    267        remainderAfterKeyBufferLength.SplitAt(keyBufferLength);
    268    auto idv =
    269        IndexDataValue{indexId, unique, Key{nsCString{AsChars(keyBuffer)}}};
    270 
    271    // Read sort key buffer length.
    272    QM_TRY_INSPECT(
    273        (const auto& [sortKeyBufferLength, remainderAfterSortKeyBufferLength]),
    274        ReadCompressedNumber(remainderAfterKeyBuffer));
    275 
    276    remainder = remainderAfterSortKeyBufferLength;
    277    if (sortKeyBufferLength > 0) {
    278      QM_TRY(OkIf(!remainder.IsEmpty()), NS_ERROR_FILE_CORRUPTED,
    279             IDB_REPORT_INTERNAL_ERR_LAMBDA);
    280 
    281      QM_TRY(OkIf(sortKeyBufferLength <= uint64_t(UINT32_MAX)),
    282             NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
    283 
    284      QM_TRY(OkIf(sortKeyBufferLength <= remainder.Length()),
    285             NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA);
    286 
    287      const auto [sortKeyBuffer, remainderAfterSortKeyBuffer] =
    288          remainder.SplitAt(sortKeyBufferLength);
    289      idv.mLocaleAwarePosition = Key{nsCString{AsChars(sortKeyBuffer)}};
    290      remainder = remainderAfterSortKeyBuffer;
    291    }
    292 
    293    QM_TRY(OkIf(aOutIndexValues->AppendElement(std::move(idv), fallible)),
    294           NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA);
    295  }
    296  aOutIndexValues->Sort();
    297 
    298  return NS_OK;
    299 }
    300 
    301 // aOutIndexValues is an output parameter, since its storage is reused.
    302 template <typename T>
    303 nsresult ReadCompressedIndexDataValuesFromSource(
    304    T& aSource, uint32_t aColumnIndex,
    305    nsTArray<IndexDataValue>* aOutIndexValues) {
    306  MOZ_ASSERT(!NS_IsMainThread());
    307  MOZ_ASSERT(!IsOnBackgroundThread());
    308  MOZ_ASSERT(aOutIndexValues);
    309  MOZ_ASSERT(aOutIndexValues->IsEmpty());
    310 
    311  QM_TRY_INSPECT(
    312      const int32_t& columnType,
    313      MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aColumnIndex));
    314 
    315  switch (columnType) {
    316    case mozIStorageStatement::VALUE_TYPE_NULL:
    317      return NS_OK;
    318 
    319    case mozIStorageStatement::VALUE_TYPE_BLOB: {
    320      // XXX ToResultInvoke does not support multiple output parameters yet, so
    321      // we also can't use QM_TRY_UNWRAP/QM_TRY_INSPECT here.
    322      const uint8_t* blobData;
    323      uint32_t blobDataLength;
    324      QM_TRY(MOZ_TO_RESULT(
    325          aSource.GetSharedBlob(aColumnIndex, &blobDataLength, &blobData)));
    326 
    327      QM_TRY(OkIf(blobDataLength), NS_ERROR_FILE_CORRUPTED,
    328             IDB_REPORT_INTERNAL_ERR_LAMBDA);
    329 
    330      QM_TRY(MOZ_TO_RESULT(ReadCompressedIndexDataValuesFromBlob(
    331          Span(blobData, blobDataLength), aOutIndexValues)));
    332 
    333      return NS_OK;
    334    }
    335 
    336    default:
    337      return NS_ERROR_FILE_CORRUPTED;
    338  }
    339 }
    340 
    341 Result<StructuredCloneReadInfoParent, nsresult>
    342 GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
    343                                   uint32_t aBlobDataLength,
    344                                   const DatabaseFileManager& aFileManager,
    345                                   const nsAString& aFileIds) {
    346  MOZ_ASSERT(!IsOnBackgroundThread());
    347 
    348  AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromBlob", DOM);
    349 
    350  const char* const compressed = reinterpret_cast<const char*>(aBlobData);
    351  const size_t compressedLength = size_t(aBlobDataLength);
    352 
    353  size_t uncompressedLength;
    354  QM_TRY(OkIf(snappy::GetUncompressedLength(compressed, compressedLength,
    355                                            &uncompressedLength)),
    356         Err(NS_ERROR_FILE_CORRUPTED));
    357 
    358  // `data` (JSStructuredCloneData) currently uses 4k buffer internally.
    359  // For performance reasons, it's better to align `uncompressed` with that.
    360  AutoTArray<uint8_t, 4096> uncompressed;
    361  QM_TRY(OkIf(uncompressed.SetLength(uncompressedLength, fallible)),
    362         Err(NS_ERROR_OUT_OF_MEMORY));
    363 
    364  char* const uncompressedBuffer =
    365      reinterpret_cast<char*>(uncompressed.Elements());
    366 
    367  QM_TRY(OkIf(snappy::RawUncompress(compressed, compressedLength,
    368                                    uncompressedBuffer)),
    369         Err(NS_ERROR_FILE_CORRUPTED));
    370 
    371  JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess);
    372  QM_TRY(OkIf(data.AppendBytes(uncompressedBuffer, uncompressed.Length())),
    373         Err(NS_ERROR_OUT_OF_MEMORY));
    374 
    375  nsTArray<StructuredCloneFileParent> files;
    376  if (!aFileIds.IsVoid()) {
    377    QM_TRY_UNWRAP(files,
    378                  DeserializeStructuredCloneFiles(aFileManager, aFileIds));
    379  }
    380 
    381  return StructuredCloneReadInfoParent{std::move(data), std::move(files),
    382                                       false};
    383 }
    384 
    385 Result<StructuredCloneReadInfoParent, nsresult>
    386 GetStructuredCloneReadInfoFromExternalBlob(
    387    uint64_t aIntData, const DatabaseFileManager& aFileManager,
    388    const nsAString& aFileIds) {
    389  MOZ_ASSERT(!IsOnBackgroundThread());
    390 
    391  AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromExternalBlob", DOM);
    392 
    393  nsTArray<StructuredCloneFileParent> files;
    394  if (!aFileIds.IsVoid()) {
    395    QM_TRY_UNWRAP(files,
    396                  DeserializeStructuredCloneFiles(aFileManager, aFileIds));
    397  }
    398 
    399  // Higher and lower 32 bits described
    400  // in ObjectStoreAddOrPutRequestOp::DoDatabaseWork.
    401  const uint32_t index = uint32_t(aIntData & UINT32_MAX);
    402 
    403  QM_TRY(OkIf(index < files.Length()), Err(NS_ERROR_UNEXPECTED),
    404         [](const auto&) { MOZ_ASSERT(false, "Bad index value!"); });
    405 
    406  if (StaticPrefs::dom_indexedDB_preprocessing()) {
    407    return StructuredCloneReadInfoParent{
    408        JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess},
    409        std::move(files), true};
    410  }
    411 
    412  // XXX Why can there be multiple files, but we use only a single one here?
    413  const StructuredCloneFileParent& file = files[index];
    414  MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eStructuredClone);
    415 
    416  Maybe<CipherKey> maybeKey;
    417 
    418  if (aFileManager.IsInPrivateBrowsingMode()) {
    419    nsCString fileKeyId;
    420    fileKeyId.AppendInt(file.FileInfo().Id());
    421 
    422    maybeKey = aFileManager.MutableCipherKeyManagerRef().Get(fileKeyId);
    423  }
    424 
    425  auto data = JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess};
    426 
    427  {
    428    const nsCOMPtr<nsIFile> nativeFile = file.FileInfo().GetFileForFileInfo();
    429    QM_TRY(OkIf(nativeFile), Err(NS_ERROR_FAILURE));
    430 
    431    QM_TRY_INSPECT(
    432        const auto& fileInputStream,
    433        NS_NewLocalFileInputStream(nativeFile)
    434            .andThen([maybeKey](auto fileInputStream)
    435                         -> Result<nsCOMPtr<nsIInputStream>, nsresult> {
    436              if (maybeKey) {
    437                return nsCOMPtr<nsIInputStream>{MakeRefPtr<
    438                    quota::DecryptingInputStream<IndexedDBCipherStrategy>>(
    439                    WrapNotNull(std::move(fileInputStream)),
    440                    kEncryptedStreamBlockSize, *maybeKey)};
    441              }
    442 
    443              return fileInputStream;
    444            }));
    445 
    446    QM_TRY(MOZ_TO_RESULT(
    447        SnappyUncompressStructuredCloneData(*fileInputStream, data)));
    448  }
    449 
    450  return StructuredCloneReadInfoParent{std::move(data), std::move(files),
    451                                       false};
    452 }
    453 
    454 template <typename T>
    455 Result<StructuredCloneReadInfoParent, nsresult>
    456 GetStructuredCloneReadInfoFromSource(T* aSource, uint32_t aDataIndex,
    457                                     uint32_t aFileIdsIndex,
    458                                     const DatabaseFileManager& aFileManager) {
    459  MOZ_ASSERT(!IsOnBackgroundThread());
    460  MOZ_ASSERT(aSource);
    461 
    462  QM_TRY_INSPECT(
    463      const int32_t& columnType,
    464      MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aDataIndex));
    465 
    466  QM_TRY_INSPECT(const bool& isNull, MOZ_TO_RESULT_INVOKE_MEMBER(
    467                                         aSource, GetIsNull, aFileIdsIndex));
    468 
    469  QM_TRY_INSPECT(const nsString& fileIds, ([aSource, aFileIdsIndex, isNull] {
    470                   return isNull ? Result<nsString, nsresult>{VoidString()}
    471                                 : MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    472                                       nsString, aSource, GetString,
    473                                       aFileIdsIndex);
    474                 }()));
    475 
    476  switch (columnType) {
    477    case mozIStorageStatement::VALUE_TYPE_INTEGER: {
    478      QM_TRY_INSPECT(
    479          const int64_t& intData,
    480          MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetInt64, aDataIndex));
    481 
    482      uint64_t uintData;
    483      memcpy(&uintData, &intData, sizeof(uint64_t));
    484 
    485      return GetStructuredCloneReadInfoFromExternalBlob(uintData, aFileManager,
    486                                                        fileIds);
    487    }
    488 
    489    case mozIStorageStatement::VALUE_TYPE_BLOB: {
    490      const uint8_t* blobData;
    491      uint32_t blobDataLength;
    492      QM_TRY(MOZ_TO_RESULT(
    493          aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData)));
    494 
    495      return GetStructuredCloneReadInfoFromBlob(blobData, blobDataLength,
    496                                                aFileManager, fileIds);
    497    }
    498 
    499    default:
    500      return Err(NS_ERROR_FILE_CORRUPTED);
    501  }
    502 }
    503 
    504 }  // namespace
    505 
    506 IndexDataValue::IndexDataValue() : mIndexId(0), mUnique(false) {
    507  MOZ_COUNT_CTOR(IndexDataValue);
    508 }
    509 
    510 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
    511 IndexDataValue::IndexDataValue(IndexDataValue&& aOther) noexcept
    512    : mIndexId(aOther.mIndexId),
    513      mPosition(std::move(aOther.mPosition)),
    514      mLocaleAwarePosition(std::move(aOther.mLocaleAwarePosition)),
    515      mUnique(aOther.mUnique) {
    516  MOZ_ASSERT(!aOther.mPosition.IsUnset());
    517 
    518  MOZ_COUNT_CTOR(IndexDataValue);
    519 }
    520 #endif
    521 
    522 IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
    523                               const Key& aPosition)
    524    : mIndexId(aIndexId), mPosition(aPosition), mUnique(aUnique) {
    525  MOZ_ASSERT(!aPosition.IsUnset());
    526 
    527  MOZ_COUNT_CTOR(IndexDataValue);
    528 }
    529 
    530 IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
    531                               const Key& aPosition,
    532                               const Key& aLocaleAwarePosition)
    533    : mIndexId(aIndexId),
    534      mPosition(aPosition),
    535      mLocaleAwarePosition(aLocaleAwarePosition),
    536      mUnique(aUnique) {
    537  MOZ_ASSERT(!aPosition.IsUnset());
    538 
    539  MOZ_COUNT_CTOR(IndexDataValue);
    540 }
    541 
    542 bool IndexDataValue::operator==(const IndexDataValue& aOther) const {
    543  if (mIndexId != aOther.mIndexId) {
    544    return false;
    545  }
    546  if (mLocaleAwarePosition.IsUnset()) {
    547    return mPosition == aOther.mPosition;
    548  }
    549  return mLocaleAwarePosition == aOther.mLocaleAwarePosition;
    550 }
    551 
    552 bool IndexDataValue::operator<(const IndexDataValue& aOther) const {
    553  if (mIndexId == aOther.mIndexId) {
    554    if (mLocaleAwarePosition.IsUnset()) {
    555      return mPosition < aOther.mPosition;
    556    }
    557    return mLocaleAwarePosition < aOther.mLocaleAwarePosition;
    558  }
    559 
    560  return mIndexId < aOther.mIndexId;
    561 }
    562 
    563 JSObject* GetSandbox(JSContext* aCx) {
    564  SandboxHolder* holder = SandboxHolder::GetOrCreate();
    565  return holder->GetSandboxInternal(aCx);
    566 }
    567 
    568 Result<std::pair<UniqueFreePtr<uint8_t>, uint32_t>, nsresult>
    569 MakeCompressedIndexDataValues(const nsTArray<IndexDataValue>& aIndexValues) {
    570  MOZ_ASSERT(!NS_IsMainThread());
    571  MOZ_ASSERT(!IsOnBackgroundThread());
    572 
    573  AUTO_PROFILER_LABEL("MakeCompressedIndexDataValues", DOM);
    574 
    575  const uint32_t arrayLength = aIndexValues.Length();
    576  if (!arrayLength) {
    577    return std::pair{UniqueFreePtr<uint8_t>{}, 0u};
    578  }
    579 
    580  // First calculate the size of the final buffer.
    581  const auto blobDataLength = std::accumulate(
    582      aIndexValues.cbegin(), aIndexValues.cend(), CheckedUint32(0),
    583      [](CheckedUint32 sum, const IndexDataValue& info) {
    584        const nsCString& keyBuffer = info.mPosition.GetBuffer();
    585        const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
    586        const uint32_t keyBufferLength = keyBuffer.Length();
    587        const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
    588 
    589        MOZ_ASSERT(!keyBuffer.IsEmpty());
    590 
    591        return sum + CompressedByteCountForIndexId(info.mIndexId) +
    592               CompressedByteCountForNumber(keyBufferLength) +
    593               CompressedByteCountForNumber(sortKeyBufferLength) +
    594               keyBufferLength + sortKeyBufferLength;
    595      });
    596 
    597  QM_TRY(OkIf(blobDataLength.isValid()),
    598         Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
    599         IDB_REPORT_INTERNAL_ERR_LAMBDA);
    600 
    601  UniqueFreePtr<uint8_t> blobData(
    602      static_cast<uint8_t*>(malloc(blobDataLength.value())));
    603  QM_TRY(OkIf(static_cast<bool>(blobData)), Err(NS_ERROR_OUT_OF_MEMORY),
    604         IDB_REPORT_INTERNAL_ERR_LAMBDA);
    605 
    606  uint8_t* blobDataIter = blobData.get();
    607 
    608  for (const IndexDataValue& info : aIndexValues) {
    609    const nsCString& keyBuffer = info.mPosition.GetBuffer();
    610    const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
    611    const uint32_t keyBufferLength = keyBuffer.Length();
    612    const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
    613 
    614    WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
    615    WriteCompressedNumber(keyBufferLength, &blobDataIter);
    616 
    617    memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
    618    blobDataIter += keyBufferLength;
    619 
    620    WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
    621 
    622    memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
    623    blobDataIter += sortKeyBufferLength;
    624  }
    625 
    626  MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength.value());
    627 
    628  return std::pair{std::move(blobData), blobDataLength.value()};
    629 }
    630 
    631 nsresult ReadCompressedIndexDataValues(
    632    mozIStorageStatement& aStatement, uint32_t aColumnIndex,
    633    nsTArray<IndexDataValue>& aOutIndexValues) {
    634  return ReadCompressedIndexDataValuesFromSource(aStatement, aColumnIndex,
    635                                                 &aOutIndexValues);
    636 }
    637 
    638 template <typename T>
    639 Result<IndexDataValuesAutoArray, nsresult> ReadCompressedIndexDataValues(
    640    T& aValues, uint32_t aColumnIndex) {
    641  return MOZ_TO_RESULT_INVOKE_TYPED(IndexDataValuesAutoArray,
    642                                    &ReadCompressedIndexDataValuesFromSource<T>,
    643                                    aValues, aColumnIndex);
    644 }
    645 
    646 template Result<IndexDataValuesAutoArray, nsresult>
    647 ReadCompressedIndexDataValues<mozIStorageValueArray>(mozIStorageValueArray&,
    648                                                     uint32_t);
    649 
    650 template Result<IndexDataValuesAutoArray, nsresult>
    651 ReadCompressedIndexDataValues<mozIStorageStatement>(mozIStorageStatement&,
    652                                                    uint32_t);
    653 
    654 Result<std::tuple<IndexOrObjectStoreId, bool, Span<const uint8_t>>, nsresult>
    655 ReadCompressedIndexId(const Span<const uint8_t> aData) {
    656  QM_TRY_INSPECT((const auto& [indexId, remainder]),
    657                 ReadCompressedNumber(aData));
    658 
    659  MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
    660 
    661  return std::tuple{IndexOrObjectStoreId(indexId >> 1), indexId % 2 == 1,
    662                    remainder};
    663 }
    664 
    665 Result<std::pair<uint64_t, mozilla::Span<const uint8_t>>, nsresult>
    666 ReadCompressedNumber(const Span<const uint8_t> aSpan) {
    667  uint8_t shiftCounter = 0;
    668  uint64_t result = 0;
    669 
    670  const auto end = aSpan.cend();
    671 
    672  const auto newPos =
    673      std::find_if(aSpan.cbegin(), end, [&result, &shiftCounter](uint8_t byte) {
    674        MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
    675 
    676        result += (uint64_t(byte & 0x7f) << shiftCounter);
    677        shiftCounter += 7;
    678 
    679        return !(byte & 0x80);
    680      });
    681 
    682  QM_TRY(OkIf(newPos != end), Err(NS_ERROR_FILE_CORRUPTED), [](const auto&) {
    683    MOZ_ASSERT(false);
    684    IDB_REPORT_INTERNAL_ERR();
    685  });
    686 
    687  return std::pair{result, Span{newPos + 1, end}};
    688 }
    689 
    690 Result<StructuredCloneReadInfoParent, nsresult>
    691 GetStructuredCloneReadInfoFromValueArray(
    692    mozIStorageValueArray* aValues, uint32_t aDataIndex, uint32_t aFileIdsIndex,
    693    const DatabaseFileManager& aFileManager) {
    694  return GetStructuredCloneReadInfoFromSource(aValues, aDataIndex,
    695                                              aFileIdsIndex, aFileManager);
    696 }
    697 
    698 Result<StructuredCloneReadInfoParent, nsresult>
    699 GetStructuredCloneReadInfoFromStatement(
    700    mozIStorageStatement* aStatement, uint32_t aDataIndex,
    701    uint32_t aFileIdsIndex, const DatabaseFileManager& aFileManager) {
    702  return GetStructuredCloneReadInfoFromSource(aStatement, aDataIndex,
    703                                              aFileIdsIndex, aFileManager);
    704 }
    705 
    706 Result<nsTArray<StructuredCloneFileParent>, nsresult>
    707 DeserializeStructuredCloneFiles(const DatabaseFileManager& aFileManager,
    708                                const nsAString& aText) {
    709  MOZ_ASSERT(!IsOnBackgroundThread());
    710 
    711  nsTArray<StructuredCloneFileParent> result;
    712  for (const auto& token :
    713       nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>(aText, ' ')
    714           .ToRange()) {
    715    MOZ_ASSERT(!token.IsEmpty());
    716 
    717    QM_TRY_UNWRAP(auto structuredCloneFile,
    718                  DeserializeStructuredCloneFile(aFileManager, token));
    719 
    720    result.EmplaceBack(std::move(structuredCloneFile));
    721  }
    722 
    723  return result;
    724 }
    725 
    726 nsresult ExecuteSimpleSQLSequence(mozIStorageConnection& aConnection,
    727                                  Span<const nsLiteralCString> aSQLCommands) {
    728  for (const auto& aSQLCommand : aSQLCommands) {
    729    const auto extraInfo = quota::ScopedLogExtraInfo{
    730        quota::ScopedLogExtraInfo::kTagQueryTainted, aSQLCommand};
    731 
    732    QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(aSQLCommand)));
    733  }
    734 
    735  return NS_OK;
    736 }
    737 
    738 }  // namespace mozilla::dom::indexedDB