/* -*- 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 "FileSystemDirectoryIteratorFactory.h" #include "FileSystemEntryMetadataArray.h" #include "fs/FileSystemRequestHandler.h" #include "jsapi.h" #include "mozilla/dom/FileSystemDirectoryHandle.h" #include "mozilla/dom/FileSystemDirectoryIterator.h" #include "mozilla/dom/FileSystemFileHandle.h" #include "mozilla/dom/FileSystemHandle.h" #include "mozilla/dom/FileSystemLog.h" #include "mozilla/dom/FileSystemManager.h" #include "mozilla/dom/IterableIterator.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseNativeHandler.h" namespace mozilla::dom::fs { namespace { template struct ValueResolver; template <> struct ValueResolver { void operator()(nsIGlobalObject* aGlobal, RefPtr& aManager, const FileSystemEntryMetadata& aValue, const RefPtr& aPromise) { aPromise->MaybeResolve(aValue.entryName()); } }; template <> struct ValueResolver { void operator()(nsIGlobalObject* aGlobal, RefPtr& aManager, const FileSystemEntryMetadata& aValue, const RefPtr& aPromise) { RefPtr handle; if (aValue.directory()) { handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue); } else { handle = new FileSystemFileHandle(aGlobal, aManager, aValue); } aPromise->MaybeResolve(std::move(handle)); } }; template <> struct ValueResolver { void operator()(nsIGlobalObject* aGlobal, RefPtr& aManager, const FileSystemEntryMetadata& aValue, const RefPtr& aPromise) { RefPtr handle; if (aValue.directory()) { handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue); } else { handle = new FileSystemFileHandle(aGlobal, aManager, aValue); } iterator_utils::ResolvePromiseWithKeyAndValue(aPromise, aValue.entryName(), handle); } }; // TODO: PageSize could be compile-time shared between content and parent template class DoubleBufferQueueImpl : public mozilla::dom::FileSystemDirectoryIterator::Impl { static_assert(PageSize > 0u); public: using DataType = FileSystemEntryMetadata; explicit DoubleBufferQueueImpl(const FileSystemEntryMetadata& aMetadata) : mEntryId(aMetadata.entryId()), mData(), mWithinPageEnd(0u), mWithinPageIndex(0u), mCurrentPageIsLastPage(true), mPageNumber(0u) {} // XXX This doesn't have to be public void ResolveValue(nsIGlobalObject* aGlobal, RefPtr& aManager, const Maybe& aValue, RefPtr aPromise) { MOZ_ASSERT(aPromise); MOZ_ASSERT(aPromise.get()); if (!aValue) { iterator_utils::ResolvePromiseForFinished(aPromise); return; } ValueResolver{}(aGlobal, aManager, *aValue, aPromise); } already_AddRefed Next(nsIGlobalObject* aGlobal, RefPtr& aManager, ErrorResult& aError) override { RefPtr promise = Promise::Create(aGlobal, aError); if (aError.Failed()) { return nullptr; } next(aGlobal, aManager, promise, aError); if (aError.Failed()) { return nullptr; } return promise.forget(); } ~DoubleBufferQueueImpl() = default; protected: void next(nsIGlobalObject* aGlobal, RefPtr& aManager, RefPtr aResult, ErrorResult& aError) { LOG_VERBOSE(("next")); MOZ_ASSERT(aResult); Maybe rawValue; // TODO: Would it be better to prefetch the items before // we hit the end of a page? // How likely it is that it would that lead to wasted fetch operations? if (0u == mWithinPageIndex) { RefPtr promise = Promise::Create(aGlobal, aError); if (aError.Failed()) { return; } auto newPage = MakeRefPtr(); RefPtr listener = new DomPromiseListener( [global = nsCOMPtr(aGlobal), manager = RefPtr(aManager), newPage, aResult, this](JSContext* aCx, JS::Handle aValue) mutable { MOZ_ASSERT(0u == mWithinPageIndex); MOZ_ASSERT(newPage->Length() <= PageSize); const size_t startPos = mCurrentPageIsLastPage ? 0u : PageSize; if (mData.Length() < 2 * PageSize) { mData.InsertElementsAt(startPos, newPage->Elements(), newPage->Length()); } else { mData.ReplaceElementsAt(startPos, newPage->Length(), newPage->Elements(), newPage->Length()); } MOZ_ASSERT(mData.Length() <= 2 * PageSize); mWithinPageEnd = newPage->Length(); Maybe value; if (0 != newPage->Length()) { nextInternal(value); } ResolveValue(global, manager, value, aResult); }, [aResult](nsresult aRv) { aResult->MaybeReject(aRv); }); promise->AppendNativeHandler(listener); FileSystemRequestHandler{}.GetEntries(aManager, mEntryId, mPageNumber, promise, newPage, aError); if (aError.Failed()) { return; } ++mPageNumber; return; } nextInternal(rawValue); ResolveValue(aGlobal, aManager, rawValue, aResult); } bool nextInternal(Maybe& aNext) { if (mWithinPageIndex >= mWithinPageEnd) { return false; } const size_t previous = mWithinPageIndex + (mCurrentPageIsLastPage ? 0 : PageSize); MOZ_ASSERT(2u * PageSize > previous); MOZ_ASSERT(previous < mData.Length()); ++mWithinPageIndex; if (mWithinPageIndex == PageSize) { // Page end reached mWithinPageIndex = 0u; mCurrentPageIsLastPage = !mCurrentPageIsLastPage; } aNext = Some(mData[previous]); return true; } const EntryId mEntryId; nsTArray mData; // TODO: Fixed size above one page? size_t mWithinPageEnd = 0u; size_t mWithinPageIndex = 0u; bool mCurrentPageIsLastPage = true; // In the beginning, first page is free PageNumber mPageNumber = 0u; }; template using UnderlyingQueue = DoubleBufferQueueImpl>; } // namespace UniquePtr FileSystemDirectoryIteratorFactory::Create( const FileSystemEntryMetadata& aMetadata, IterableIteratorBase::IteratorType aType) { if (IterableIteratorBase::Entries == aType) { return MakeUnique>( aMetadata); } if (IterableIteratorBase::Values == aType) { return MakeUnique>(aMetadata); } return MakeUnique>(aMetadata); } } // namespace mozilla::dom::fs