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