tor-browser

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

FileSystemDirectoryIteratorFactory.cpp (7979B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "FileSystemDirectoryIteratorFactory.h"
      8 
      9 #include "FileSystemEntryMetadataArray.h"
     10 #include "fs/FileSystemRequestHandler.h"
     11 #include "jsapi.h"
     12 #include "mozilla/dom/FileSystemDirectoryHandle.h"
     13 #include "mozilla/dom/FileSystemDirectoryIterator.h"
     14 #include "mozilla/dom/FileSystemFileHandle.h"
     15 #include "mozilla/dom/FileSystemHandle.h"
     16 #include "mozilla/dom/FileSystemLog.h"
     17 #include "mozilla/dom/FileSystemManager.h"
     18 #include "mozilla/dom/IterableIterator.h"
     19 #include "mozilla/dom/Promise-inl.h"
     20 #include "mozilla/dom/Promise.h"
     21 #include "mozilla/dom/PromiseNativeHandler.h"
     22 
     23 namespace mozilla::dom::fs {
     24 
     25 namespace {
     26 
     27 template <IterableIteratorBase::IteratorType Type>
     28 struct ValueResolver;
     29 
     30 template <>
     31 struct ValueResolver<IterableIteratorBase::Keys> {
     32  void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
     33                  const FileSystemEntryMetadata& aValue,
     34                  const RefPtr<Promise>& aPromise) {
     35    aPromise->MaybeResolve(aValue.entryName());
     36  }
     37 };
     38 
     39 template <>
     40 struct ValueResolver<IterableIteratorBase::Values> {
     41  void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
     42                  const FileSystemEntryMetadata& aValue,
     43                  const RefPtr<Promise>& aPromise) {
     44    RefPtr<FileSystemHandle> handle;
     45 
     46    if (aValue.directory()) {
     47      handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue);
     48    } else {
     49      handle = new FileSystemFileHandle(aGlobal, aManager, aValue);
     50    }
     51 
     52    aPromise->MaybeResolve(std::move(handle));
     53  }
     54 };
     55 
     56 template <>
     57 struct ValueResolver<IterableIteratorBase::Entries> {
     58  void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
     59                  const FileSystemEntryMetadata& aValue,
     60                  const RefPtr<Promise>& aPromise) {
     61    RefPtr<FileSystemHandle> handle;
     62 
     63    if (aValue.directory()) {
     64      handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue);
     65    } else {
     66      handle = new FileSystemFileHandle(aGlobal, aManager, aValue);
     67    }
     68 
     69    iterator_utils::ResolvePromiseWithKeyAndValue(aPromise, aValue.entryName(),
     70                                                  handle);
     71  }
     72 };
     73 
     74 // TODO: PageSize could be compile-time shared between content and parent
     75 template <class ValueResolver, size_t PageSize = 1024u>
     76 class DoubleBufferQueueImpl
     77    : public mozilla::dom::FileSystemDirectoryIterator::Impl {
     78  static_assert(PageSize > 0u);
     79 
     80 public:
     81  using DataType = FileSystemEntryMetadata;
     82  explicit DoubleBufferQueueImpl(const FileSystemEntryMetadata& aMetadata)
     83      : mEntryId(aMetadata.entryId()),
     84        mWithinPageEnd(0u),
     85        mWithinPageIndex(0u),
     86        mCurrentPageIsLastPage(true),
     87        mPageNumber(0u) {}
     88 
     89  // XXX This doesn't have to be public
     90  void ResolveValue(nsIGlobalObject* aGlobal,
     91                    RefPtr<FileSystemManager>& aManager,
     92                    const Maybe<DataType>& aValue, RefPtr<Promise> aPromise) {
     93    MOZ_ASSERT(aPromise);
     94    MOZ_ASSERT(aPromise.get());
     95 
     96    if (!aValue) {
     97      iterator_utils::ResolvePromiseForFinished(aPromise);
     98      return;
     99    }
    100 
    101    ValueResolver{}(aGlobal, aManager, *aValue, aPromise);
    102  }
    103 
    104  already_AddRefed<Promise> Next(nsIGlobalObject* aGlobal,
    105                                 RefPtr<FileSystemManager>& aManager,
    106                                 ErrorResult& aError) override {
    107    RefPtr<Promise> promise = Promise::Create(aGlobal, aError);
    108    if (aError.Failed()) {
    109      return nullptr;
    110    }
    111 
    112    next(aGlobal, aManager, promise, aError);
    113    if (aError.Failed()) {
    114      return nullptr;
    115    }
    116 
    117    return promise.forget();
    118  }
    119 
    120 protected:
    121  ~DoubleBufferQueueImpl() override = default;
    122 
    123  void next(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
    124            RefPtr<Promise> aResult, ErrorResult& aError) {
    125    LOG_VERBOSE(("next"));
    126    MOZ_ASSERT(aResult);
    127 
    128    Maybe<DataType> rawValue;
    129 
    130    // TODO: Would it be better to prefetch the items before
    131    // we hit the end of a page?
    132    // How likely it is that it would that lead to wasted fetch operations?
    133    if (0u == mWithinPageIndex) {
    134      RefPtr<Promise> promise = Promise::Create(aGlobal, aError);
    135      if (aError.Failed()) {
    136        return;
    137      }
    138 
    139      auto newPage = MakeRefPtr<FileSystemEntryMetadataArray>();
    140 
    141      promise->AddCallbacksWithCycleCollectedArgs(
    142          [self = RefPtr(this), newPage](
    143              JSContext*, JS::Handle<JS::Value>, ErrorResult&,
    144              RefPtr<FileSystemManager>& aManager, RefPtr<Promise>& aResult) {
    145            MOZ_ASSERT(0u == self->mWithinPageIndex);
    146            MOZ_ASSERT(newPage->Length() <= PageSize);
    147 
    148            const size_t startPos =
    149                self->mCurrentPageIsLastPage ? 0u : PageSize;
    150            if (self->mData.Length() < 2 * PageSize) {
    151              self->mData.InsertElementsAt(startPos, newPage->Elements(),
    152                                           newPage->Length());
    153            } else {
    154              self->mData.ReplaceElementsAt(startPos, newPage->Length(),
    155                                            newPage->Elements(),
    156                                            newPage->Length());
    157            }
    158            MOZ_ASSERT(self->mData.Length() <= 2 * PageSize);
    159            self->mWithinPageEnd = newPage->Length();
    160 
    161            Maybe<DataType> value;
    162            if (0 != newPage->Length()) {
    163              self->nextInternal(value);
    164            }
    165 
    166            self->ResolveValue(aResult->GetGlobalObject(), aManager, value,
    167                               aResult);
    168          },
    169          [](JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&,
    170             RefPtr<FileSystemManager>&,
    171             RefPtr<Promise>& aResult) { aResult->MaybeReject(aValue); },
    172          aManager, aResult);
    173 
    174      FileSystemRequestHandler{}.GetEntries(aManager, mEntryId, mPageNumber,
    175                                            promise, newPage, aError);
    176      if (aError.Failed()) {
    177        return;
    178      }
    179 
    180      ++mPageNumber;
    181      return;
    182    }
    183 
    184    nextInternal(rawValue);
    185 
    186    ResolveValue(aGlobal, aManager, rawValue, aResult);
    187  }
    188 
    189  bool nextInternal(Maybe<DataType>& aNext) {
    190    if (mWithinPageIndex >= mWithinPageEnd) {
    191      return false;
    192    }
    193 
    194    const size_t previous =
    195        mWithinPageIndex + (mCurrentPageIsLastPage ? 0 : PageSize);
    196    MOZ_ASSERT(2u * PageSize > previous);
    197    MOZ_ASSERT(previous < mData.Length());
    198 
    199    ++mWithinPageIndex;
    200 
    201    if (mWithinPageIndex == PageSize) {
    202      // Page end reached
    203      mWithinPageIndex = 0u;
    204      mCurrentPageIsLastPage = !mCurrentPageIsLastPage;
    205    }
    206 
    207    aNext = Some(mData[previous]);
    208    return true;
    209  }
    210 
    211  const EntryId mEntryId;
    212 
    213  nsTArray<DataType> mData;  // TODO: Fixed size above one page?
    214 
    215  size_t mWithinPageEnd = 0u;
    216  size_t mWithinPageIndex = 0u;
    217  bool mCurrentPageIsLastPage = true;  // In the beginning, first page is free
    218  PageNumber mPageNumber = 0u;
    219 };
    220 
    221 template <IterableIteratorBase::IteratorType Type>
    222 using UnderlyingQueue = DoubleBufferQueueImpl<ValueResolver<Type>>;
    223 
    224 }  // namespace
    225 
    226 already_AddRefed<mozilla::dom::FileSystemDirectoryIterator::Impl>
    227 FileSystemDirectoryIteratorFactory::Create(
    228    const FileSystemEntryMetadata& aMetadata,
    229    IterableIteratorBase::IteratorType aType) {
    230  if (IterableIteratorBase::Entries == aType) {
    231    return MakeAndAddRef<UnderlyingQueue<IterableIteratorBase::Entries>>(
    232        aMetadata);
    233  }
    234 
    235  if (IterableIteratorBase::Values == aType) {
    236    return MakeAndAddRef<UnderlyingQueue<IterableIteratorBase::Values>>(
    237        aMetadata);
    238  }
    239 
    240  return MakeAndAddRef<UnderlyingQueue<IterableIteratorBase::Keys>>(aMetadata);
    241 }
    242 
    243 }  // namespace mozilla::dom::fs