Bug 1758324 - Implement file system directory iterator; r=dom-storage-reviewers,jesup,janv,smaug
authorJan Varga <jvarga@mozilla.com>
Tue, 06 Sep 2022 11:59:00 +0000
changeset 634675 c9e945b32a0e2b60189c9e5b6b7d0492fe61b901
parent 634674 60545f16f233b87b432e0a3fee71c41801ed54b3
child 634676 d1b399bcd0474869d29804c13b2145a6a8b645da
push id169199
push userjjalkanen@mozilla.com
push dateTue, 06 Sep 2022 12:01:25 +0000
treeherderautoland@c9e945b32a0e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdom-storage-reviewers, jesup, janv, smaug
bugs1758324
milestone106.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1758324 - Implement file system directory iterator; r=dom-storage-reviewers,jesup,janv,smaug Differential Revision: https://phabricator.services.mozilla.com/D140300
dom/fs/api/FileSystemDirectoryHandle.cpp
dom/fs/api/FileSystemDirectoryHandle.h
dom/fs/api/FileSystemDirectoryIterator.cpp
dom/fs/api/FileSystemDirectoryIterator.h
dom/fs/api/FileSystemHandle.h
dom/fs/child/ArrayAppendable.h
dom/fs/child/FileSystemDirectoryIteratorFactory.cpp
dom/fs/child/FileSystemDirectoryIteratorFactory.h
dom/fs/child/FileSystemRequestHandler.cpp
dom/fs/child/moz.build
dom/fs/include/fs/FileSystemRequestHandler.h
dom/fs/parent/FileSystemManagerParent.cpp
dom/fs/test/common/test_basics.js
dom/fs/test/common/test_fileSystemDirectoryHandle.js
dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp
dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp
dom/quota/StorageManager.cpp
dom/webidl/FileSystemDirectoryHandle.webidl
--- a/dom/fs/api/FileSystemDirectoryHandle.cpp
+++ b/dom/fs/api/FileSystemDirectoryHandle.cpp
@@ -1,17 +1,18 @@
 /* -*- 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 "FileSystemDirectoryHandle.h"
+
+#include "FileSystemDirectoryIteratorFactory.h"
 #include "fs/FileSystemRequestHandler.h"
-
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/FileSystemDirectoryHandleBinding.h"
 #include "mozilla/dom/FileSystemDirectoryIterator.h"
 #include "mozilla/dom/FileSystemHandleBinding.h"
 #include "mozilla/dom/FileSystemManager.h"
 #include "mozilla/dom/PFileSystemManager.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/StorageManager.h"
@@ -42,66 +43,89 @@ JSObject* FileSystemDirectoryHandle::Wra
 }
 
 // WebIDL Interface
 
 FileSystemHandleKind FileSystemDirectoryHandle::Kind() const {
   return FileSystemHandleKind::Directory;
 }
 
-already_AddRefed<FileSystemDirectoryIterator>
-FileSystemDirectoryHandle::Entries() {
-  return MakeRefPtr<FileSystemDirectoryIterator>(GetParentObject()).forget();
+void FileSystemDirectoryHandle::InitAsyncIterator(
+    FileSystemDirectoryHandle::iterator_t* aIterator, ErrorResult& aError) {
+  aIterator->SetData(
+      static_cast<void*>(fs::FileSystemDirectoryIteratorFactory::Create(
+                             mMetadata, aIterator->GetIteratorType())
+                             .release()));
 }
 
-already_AddRefed<FileSystemDirectoryIterator>
-FileSystemDirectoryHandle::Keys() {
-  return MakeRefPtr<FileSystemDirectoryIterator>(GetParentObject()).forget();
+void FileSystemDirectoryHandle::DestroyAsyncIterator(
+    FileSystemDirectoryHandle::iterator_t* aIterator) {
+  auto* it =
+      static_cast<FileSystemDirectoryIterator::Impl*>(aIterator->GetData());
+  delete it;
+  aIterator->SetData(nullptr);
 }
 
-already_AddRefed<FileSystemDirectoryIterator>
-FileSystemDirectoryHandle::Values() {
-  return MakeRefPtr<FileSystemDirectoryIterator>(GetParentObject()).forget();
+already_AddRefed<Promise> FileSystemDirectoryHandle::GetNextPromise(
+    JSContext* /* aCx */, FileSystemDirectoryHandle::iterator_t* aIterator,
+    ErrorResult& aError) {
+  return static_cast<FileSystemDirectoryIterator::Impl*>(aIterator->GetData())
+      ->Next(mGlobal, mManager, aError);
 }
 
 already_AddRefed<Promise> FileSystemDirectoryHandle::GetFileHandle(
     const nsAString& aName, const FileSystemGetFileOptions& aOptions,
     ErrorResult& aError) {
+  MOZ_ASSERT(!mMetadata.entryId().IsEmpty());
+
   RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
-  promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
+  fs::Name name(aName);
+  fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name);
+  mRequestHandler->GetFileHandle(mManager, metadata, aOptions.mCreate, promise);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise> FileSystemDirectoryHandle::GetDirectoryHandle(
     const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions,
     ErrorResult& aError) {
+  MOZ_ASSERT(!mMetadata.entryId().IsEmpty());
+
   RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
-  promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
+  fs::Name name(aName);
+  fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name);
+  mRequestHandler->GetDirectoryHandle(mManager, metadata, aOptions.mCreate,
+                                      promise);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise> FileSystemDirectoryHandle::RemoveEntry(
     const nsAString& aName, const FileSystemRemoveOptions& aOptions,
     ErrorResult& aError) {
+  MOZ_ASSERT(!mMetadata.entryId().IsEmpty());
+
   RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
-  promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
+  fs::Name name(aName);
+  fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name);
+
+  mRequestHandler->RemoveEntry(mManager, metadata, aOptions.mRecursive,
+                               promise);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise> FileSystemDirectoryHandle::Resolve(
     FileSystemHandle& aPossibleDescendant, ErrorResult& aError) {
   RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
   if (aError.Failed()) {
--- a/dom/fs/api/FileSystemDirectoryHandle.h
+++ b/dom/fs/api/FileSystemDirectoryHandle.h
@@ -3,30 +3,34 @@
 /* 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/. */
 
 #ifndef DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
 #define DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
 
 #include "mozilla/dom/FileSystemHandle.h"
+#include "mozilla/dom/IterableIterator.h"
+#include "mozilla/dom/FileSystemDirectoryIterator.h"
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 class FileSystemDirectoryIterator;
 struct FileSystemGetFileOptions;
 struct FileSystemGetDirectoryOptions;
 struct FileSystemRemoveOptions;
 
 class FileSystemDirectoryHandle final : public FileSystemHandle {
  public:
+  using iterator_t = AsyncIterableIterator<FileSystemDirectoryHandle>;
+
   FileSystemDirectoryHandle(nsIGlobalObject* aGlobal,
                             RefPtr<FileSystemManager>& aManager,
                             const fs::FileSystemEntryMetadata& aMetadata,
                             fs::FileSystemRequestHandler* aRequestHandler);
 
   FileSystemDirectoryHandle(nsIGlobalObject* aGlobal,
                             RefPtr<FileSystemManager>& aManager,
                             const fs::FileSystemEntryMetadata& aMetadata);
@@ -37,21 +41,23 @@ class FileSystemDirectoryHandle final : 
 
   // WebIDL Boilerplate
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL Interface
   FileSystemHandleKind Kind() const override;
 
-  [[nodiscard]] already_AddRefed<FileSystemDirectoryIterator> Entries();
+  void InitAsyncIterator(iterator_t* aIterator, ErrorResult& aError);
+
+  void DestroyAsyncIterator(iterator_t* aIterator);
 
-  [[nodiscard]] already_AddRefed<FileSystemDirectoryIterator> Keys();
-
-  [[nodiscard]] already_AddRefed<FileSystemDirectoryIterator> Values();
+  [[nodiscard]] already_AddRefed<Promise> GetNextPromise(JSContext* aCx,
+                                                         iterator_t* aIterator,
+                                                         ErrorResult& aError);
 
   already_AddRefed<Promise> GetFileHandle(
       const nsAString& aName, const FileSystemGetFileOptions& aOptions,
       ErrorResult& aError);
 
   already_AddRefed<Promise> GetDirectoryHandle(
       const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions,
       ErrorResult& aError);
--- a/dom/fs/api/FileSystemDirectoryIterator.cpp
+++ b/dom/fs/api/FileSystemDirectoryIterator.cpp
@@ -3,32 +3,34 @@
 /* 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 "FileSystemDirectoryIterator.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/FileSystemDirectoryIteratorBinding.h"
+#include "mozilla/dom/FileSystemManager.h"
 #include "mozilla/dom/Promise.h"
 
 namespace mozilla::dom {
 
-FileSystemDirectoryIterator::FileSystemDirectoryIterator(
-    nsIGlobalObject* aGlobal)
-    : mGlobal(aGlobal) {}
-
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemDirectoryIterator)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemDirectoryIterator);
 NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemDirectoryIterator);
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemDirectoryIterator, mGlobal);
 
+FileSystemDirectoryIterator::FileSystemDirectoryIterator(
+    nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+    UniquePtr<Impl> aImpl)
+    : mGlobal(aGlobal), mManager(aManager), mImpl(std::move(aImpl)) {}
+
 // WebIDL Boilerplate
 
 nsIGlobalObject* FileSystemDirectoryIterator::GetParentObject() const {
   return mGlobal;
 }
 
 JSObject* FileSystemDirectoryIterator::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
@@ -39,14 +41,13 @@ JSObject* FileSystemDirectoryIterator::W
 
 already_AddRefed<Promise> FileSystemDirectoryIterator::Next(
     ErrorResult& aError) {
   RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
-  promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
-
-  return promise.forget();
+  MOZ_ASSERT(mImpl);
+  return mImpl->Next(mGlobal, mManager, aError);
 }
 
 }  // namespace mozilla::dom
--- a/dom/fs/api/FileSystemDirectoryIterator.h
+++ b/dom/fs/api/FileSystemDirectoryIterator.h
@@ -5,45 +5,66 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
 #define DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
 
 #include "nsCOMPtr.h"
 #include "nsISupports.h"
 #include "nsWrapperCache.h"
+#include "mozilla/dom/IterableIterator.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
+class FileSystemManager;
+class IterableIteratorBase;
 class Promise;
 
+// XXX This class isn't used to support iteration anymore. `Impl` should be
+// extracted elsewhere and `FileSystemDirectoryIterator` should be removed
+// completely
 class FileSystemDirectoryIterator : public nsISupports, public nsWrapperCache {
  public:
-  explicit FileSystemDirectoryIterator(nsIGlobalObject* aGlobal);
+  class Impl {
+   public:
+    virtual already_AddRefed<Promise> Next(nsIGlobalObject* aGlobal,
+                                           RefPtr<FileSystemManager>& aManager,
+                                           ErrorResult& aError) = 0;
+    virtual ~Impl() = default;
+  };
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystemDirectoryIterator)
 
+  explicit FileSystemDirectoryIterator(nsIGlobalObject* aGlobal,
+                                       RefPtr<FileSystemManager>& aManager,
+                                       UniquePtr<Impl> aImpl);
+
   // WebIDL Boilerplate
   nsIGlobalObject* GetParentObject() const;
 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL Interface
   already_AddRefed<Promise> Next(ErrorResult& aError);
 
  protected:
   virtual ~FileSystemDirectoryIterator() = default;
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
+
+  RefPtr<FileSystemManager> mManager;
+
+ private:
+  UniquePtr<Impl> mImpl;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
--- a/dom/fs/api/FileSystemHandle.h
+++ b/dom/fs/api/FileSystemHandle.h
@@ -23,17 +23,16 @@ extern LazyLogModule gOPFSLog;
 
 #define LOG_DEBUG(args) \
   MOZ_LOG(mozilla::gOPFSLog, mozilla::LogLevel::Debug, args)
 
 class ErrorResult;
 
 namespace dom {
 
-class DOMString;
 enum class FileSystemHandleKind : uint8_t;
 class FileSystemManager;
 class FileSystemManagerChild;
 class Promise;
 
 namespace fs {
 class FileSystemRequestHandler;
 }  // namespace fs
new file mode 100644
--- /dev/null
+++ b/dom/fs/child/ArrayAppendable.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#ifndef DOM_FS_ARRAYAPPENDABLE_H_
+#define DOM_FS_ARRAYAPPENDABLE_H_
+
+#include "nsTArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+class FileSystemManager;
+
+namespace fs {
+
+class FileSystemEntryMetadata;
+
+class ArrayAppendable {
+ public:
+  virtual void append(nsIGlobalObject* aGlobal,
+                      RefPtr<FileSystemManager>& aManager,
+                      const nsTArray<FileSystemEntryMetadata>& aBatch) = 0;
+
+ protected:
+  virtual ~ArrayAppendable() = default;
+};
+
+}  // namespace fs
+}  // namespace mozilla::dom
+
+#endif  // DOM_FS_ARRAYAPPENDABLE_H_
new file mode 100644
--- /dev/null
+++ b/dom/fs/child/FileSystemDirectoryIteratorFactory.cpp
@@ -0,0 +1,296 @@
+/* -*- 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 "ArrayAppendable.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/FileSystemManager.h"
+#include "mozilla/dom/IterableIterator.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom::fs {
+
+namespace {
+
+inline JSContext* GetContext(const RefPtr<Promise>& aPromise) {
+  AutoJSAPI jsapi;
+  if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) {
+    return nullptr;
+  }
+  return jsapi.cx();
+}
+
+template <IterableIteratorBase::IteratorType Type>
+struct ValueResolver;
+
+template <>
+struct ValueResolver<IterableIteratorBase::Keys> {
+  nsresult operator()(nsIGlobalObject* aGlobal,
+                      RefPtr<FileSystemManager>& aManager,
+                      const FileSystemEntryMetadata& aValue,
+                      const RefPtr<Promise>& aPromise, ErrorResult& aError) {
+    JSContext* cx = GetContext(aPromise);
+    if (!cx) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    JS::Rooted<JS::Value> key(cx);
+
+    if (!ToJSValue(cx, aValue.entryName(), &key)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    iterator_utils::ResolvePromiseWithKeyOrValue(cx, aPromise.get(), key,
+                                                 aError);
+
+    return NS_OK;
+  }
+};
+
+template <>
+struct ValueResolver<IterableIteratorBase::Values> {
+  nsresult operator()(nsIGlobalObject* aGlobal,
+                      RefPtr<FileSystemManager>& aManager,
+                      const FileSystemEntryMetadata& aValue,
+                      const RefPtr<Promise>& aPromise, ErrorResult& aError) {
+    JSContext* cx = GetContext(aPromise);
+    if (!cx) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    RefPtr<FileSystemHandle> handle;
+
+    if (aValue.directory()) {
+      handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue);
+    } else {
+      handle = new FileSystemFileHandle(aGlobal, aManager, aValue);
+    }
+
+    JS::Rooted<JS::Value> value(cx);
+    if (!ToJSValue(cx, handle, &value)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    iterator_utils::ResolvePromiseWithKeyOrValue(cx, aPromise.get(), value,
+                                                 aError);
+
+    return NS_OK;
+  }
+};
+
+template <>
+struct ValueResolver<IterableIteratorBase::Entries> {
+  nsresult operator()(nsIGlobalObject* aGlobal,
+                      RefPtr<FileSystemManager>& aManager,
+                      const FileSystemEntryMetadata& aValue,
+                      const RefPtr<Promise>& aPromise, ErrorResult& aError) {
+    JSContext* cx = GetContext(aPromise);
+    if (!cx) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    JS::Rooted<JS::Value> key(cx);
+    if (!ToJSValue(cx, aValue.entryName(), &key)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    RefPtr<FileSystemHandle> handle;
+
+    if (aValue.directory()) {
+      handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue);
+    } else {
+      handle = new FileSystemFileHandle(aGlobal, aManager, aValue);
+    }
+
+    JS::Rooted<JS::Value> value(cx);
+    if (!ToJSValue(cx, handle, &value)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    iterator_utils::ResolvePromiseWithKeyAndValue(cx, aPromise.get(), key,
+                                                  value, aError);
+
+    return NS_OK;
+  }
+};
+
+// TODO: PageSize could be compile-time shared between content and parent
+template <class ValueResolver, size_t PageSize = 1024u>
+class DoubleBufferQueueImpl
+    : public ArrayAppendable,
+      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<FileSystemManager>& aManager,
+                    const Maybe<DataType>& aValue, RefPtr<Promise> aPromise,
+                    ErrorResult& aError) {
+    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(aPromise.get());
+
+    AutoJSAPI jsapi;
+    if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) {
+      aPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
+      aError = NS_ERROR_DOM_UNKNOWN_ERR;
+      return;
+    }
+
+    JSContext* aCx = jsapi.cx();
+    MOZ_ASSERT(aCx);
+
+    if (!aValue) {
+      iterator_utils::ResolvePromiseForFinished(aCx, aPromise.get(), aError);
+      return;
+    }
+
+    ValueResolver{}(aGlobal, aManager, *aValue, aPromise, aError);
+  }
+
+  void append(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+              const nsTArray<FileSystemEntryMetadata>& aNewPage) override {
+    MOZ_ASSERT(0u == mWithinPageIndex);
+
+    nsTArray<DataType> batch;
+    for (const auto& it : aNewPage) {
+      batch.AppendElement(it);
+    }
+
+    const size_t batchSize = std::min(PageSize, aNewPage.Length());
+    mData.InsertElementsAt(
+        PageSize * static_cast<size_t>(!mCurrentPageIsLastPage),
+        batch.Elements(), batchSize);
+    mWithinPageEnd += batchSize;
+
+    if (!mPendingPromise) {
+      return;
+    }
+
+    Maybe<DataType> value;
+    if (0 != aNewPage.Length()) {
+      nextInternal(value);
+    }
+
+    IgnoredErrorResult rv;
+    ResolveValue(aGlobal, aManager, value, mPendingPromise, rv);
+
+    mPendingPromise = nullptr;
+  }
+
+  already_AddRefed<Promise> Next(nsIGlobalObject* aGlobal,
+                                 RefPtr<FileSystemManager>& aManager,
+                                 ErrorResult& aError) override {
+    RefPtr<Promise> promise = Promise::Create(aGlobal, aError);
+    if (aError.Failed()) {
+      return nullptr;
+    }
+
+    next(aGlobal, aManager, promise, aError);
+
+    return promise.forget();
+  }
+
+  ~DoubleBufferQueueImpl() = default;
+
+ protected:
+  void next(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+            RefPtr<Promise> aResult, ErrorResult& aError) {
+    MOZ_ASSERT(aResult);
+    if (mPendingPromise) {
+      aResult->MaybeRejectWithNotFoundError("Result was not awaited");
+      return;
+    }
+
+    Maybe<DataType> 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) {
+      mPendingPromise = aResult;
+      FileSystemRequestHandler{}.GetEntries(aManager, mEntryId, mPageNumber,
+                                            mPendingPromise, *this);
+      ++mPageNumber;
+      return;
+    }
+
+    nextInternal(rawValue);
+
+    ResolveValue(aGlobal, aManager, rawValue, aResult, aError);
+  }
+
+  bool nextInternal(Maybe<DataType>& aNext) {
+    if (mWithinPageIndex >= mWithinPageEnd) {
+      return false;
+    }
+
+    const auto previous =
+        static_cast<size_t>(!mCurrentPageIsLastPage) * PageSize +
+        mWithinPageIndex;
+    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<DataType> 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;
+  RefPtr<Promise> mPendingPromise;
+};
+
+template <IterableIteratorBase::IteratorType Type>
+using UnderlyingQueue = DoubleBufferQueueImpl<ValueResolver<Type>>;
+
+}  // namespace
+
+UniquePtr<mozilla::dom::FileSystemDirectoryIterator::Impl>
+FileSystemDirectoryIteratorFactory::Create(
+    const FileSystemEntryMetadata& aMetadata,
+    IterableIteratorBase::IteratorType aType) {
+  if (IterableIteratorBase::Entries == aType) {
+    return MakeUnique<UnderlyingQueue<IterableIteratorBase::Entries>>(
+        aMetadata);
+  }
+
+  if (IterableIteratorBase::Values == aType) {
+    return MakeUnique<UnderlyingQueue<IterableIteratorBase::Values>>(aMetadata);
+  }
+
+  return MakeUnique<UnderlyingQueue<IterableIteratorBase::Keys>>(aMetadata);
+}
+
+}  // namespace mozilla::dom::fs
new file mode 100644
--- /dev/null
+++ b/dom/fs/child/FileSystemDirectoryIteratorFactory.h
@@ -0,0 +1,24 @@
+/* -*- 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/. */
+
+#ifndef DOM_FS_CHILD_FILESYSTEMDIRECTORYITERATORFACTORY_H_
+#define DOM_FS_CHILD_FILESYSTEMDIRECTORYITERATORFACTORY_H_
+
+#include "mozilla/dom/FileSystemDirectoryIterator.h"
+
+namespace mozilla::dom::fs {
+
+class FileSystemEntryMetadata;
+
+struct FileSystemDirectoryIteratorFactory {
+  static UniquePtr<mozilla::dom::FileSystemDirectoryIterator::Impl> Create(
+      const FileSystemEntryMetadata& aMetadata,
+      IterableIteratorBase::IteratorType aType);
+};
+
+}  // namespace mozilla::dom::fs
+
+#endif  // DOM_FS_CHILD_FILESYSTEMDIRECTORYITERATORFACTORY_H_
--- a/dom/fs/child/FileSystemRequestHandler.cpp
+++ b/dom/fs/child/FileSystemRequestHandler.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "fs/FileSystemRequestHandler.h"
 
+#include "ArrayAppendable.h"
 #include "fs/FileSystemConstants.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileSystemDirectoryHandle.h"
 #include "mozilla/dom/FileSystemFileHandle.h"
 #include "mozilla/dom/FileSystemHandle.h"
 #include "mozilla/dom/FileSystemManager.h"
 #include "mozilla/dom/FileSystemManagerChild.h"
 #include "mozilla/dom/Promise.h"
@@ -37,31 +38,27 @@ RefPtr<File> MakeGetFileResult(nsIGlobal
 }
 
 void GetDirectoryContentsResponseHandler(
     nsIGlobalObject* aGlobal, FileSystemGetEntriesResponse&& aResponse,
     ArrayAppendable& aSink, RefPtr<FileSystemManager>& aManager) {
   // TODO: Add page size to FileSystemConstants, preallocate and handle overflow
   const auto& listing = aResponse.get_FileSystemDirectoryListing();
 
-  nsTArray<RefPtr<FileSystemHandle>> batch;
+  nsTArray<FileSystemEntryMetadata> batch;
 
   for (const auto& it : listing.files()) {
-    RefPtr<FileSystemHandle> handle =
-        new FileSystemFileHandle(aGlobal, aManager, it);
-    batch.AppendElement(handle);
+    batch.AppendElement(it);
   }
 
   for (const auto& it : listing.directories()) {
-    RefPtr<FileSystemHandle> handle =
-        new FileSystemDirectoryHandle(aGlobal, aManager, it);
-    batch.AppendElement(handle);
+    batch.AppendElement(it);
   }
 
-  aSink.append(batch);
+  aSink.append(aGlobal, aManager, batch);
 }
 
 RefPtr<FileSystemDirectoryHandle> MakeResolution(
     nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse,
     const RefPtr<FileSystemDirectoryHandle>& /* aResult */,
     RefPtr<FileSystemManager>& aManager) {
   RefPtr<FileSystemDirectoryHandle> result = new FileSystemDirectoryHandle(
       aGlobal, aManager,
--- a/dom/fs/child/moz.build
+++ b/dom/fs/child/moz.build
@@ -6,16 +6,17 @@
 
 EXPORTS.mozilla.dom += [
     "FileSystemManagerChild.h",
 ]
 
 UNIFIED_SOURCES += [
     "FileSystemBackgroundRequestHandler.cpp",
     "FileSystemChildFactory.cpp",
+    "FileSystemDirectoryIteratorFactory.cpp",
     "FileSystemRequestHandler.cpp",
 ]
 
 LOCAL_INCLUDES += [
     "/dom/fs/include",
 ]
 
 FINAL_LIBRARY = "xul"
--- a/dom/fs/include/fs/FileSystemRequestHandler.h
+++ b/dom/fs/include/fs/FileSystemRequestHandler.h
@@ -15,25 +15,21 @@ class RefPtr;
 namespace mozilla::dom {
 
 class FileSystemHandle;
 class FileSystemManager;
 class Promise;
 
 namespace fs {
 
+class ArrayAppendable;
 class FileSystemChildMetadata;
 class FileSystemEntryMetadata;
 class FileSystemEntryPair;
 
-class ArrayAppendable {
- public:
-  void append(const nsTArray<RefPtr<FileSystemHandle>>& /* aBatch */) {}
-};
-
 class FileSystemRequestHandler {
  public:
   virtual void GetRootHandle(RefPtr<FileSystemManager> aManager,
                              RefPtr<Promise> aPromise);
 
   virtual void GetDirectoryHandle(RefPtr<FileSystemManager>& aManager,
                                   const FileSystemChildMetadata& aDirectory,
                                   bool aCreate, RefPtr<Promise> aPromise);
--- a/dom/fs/parent/FileSystemManagerParent.cpp
+++ b/dom/fs/parent/FileSystemManagerParent.cpp
@@ -136,17 +136,30 @@ IPCResult FileSystemManagerParent::RecvR
   FileSystemResolveResponse response(Some(FileSystemPath(filePath)));
   aResolver(response);
 
   return IPC_OK();
 }
 
 IPCResult FileSystemManagerParent::RecvGetEntriesMsg(
     FileSystemGetEntriesRequest&& aRequest, GetEntriesMsgResolver&& aResolver) {
-  FileSystemGetEntriesResponse response(NS_ERROR_NOT_IMPLEMENTED);
+  MOZ_ASSERT(!aRequest.parentId().IsEmpty());
+  MOZ_ASSERT(mDataManager);
+
+  auto reportError = [&aResolver](const QMResult& aRv) {
+    FileSystemGetEntriesResponse response(ToNSResult(aRv));
+    aResolver(response);
+  };
+
+  QM_TRY_UNWRAP(FileSystemDirectoryListing entries,
+                mDataManager->MutableDatabaseManagerPtr()->GetDirectoryEntries(
+                    aRequest.parentId(), aRequest.page()),
+                IPC_OK(), reportError);
+
+  FileSystemGetEntriesResponse response(entries);
   aResolver(response);
 
   return IPC_OK();
 }
 
 IPCResult FileSystemManagerParent::RecvRemoveEntryMsg(
     FileSystemRemoveEntryRequest&& aRequest,
     RemoveEntryMsgResolver&& aResolver) {
--- a/dom/fs/test/common/test_basics.js
+++ b/dom/fs/test/common/test_basics.js
@@ -45,131 +45,85 @@ exported_symbols.testDirectoryHandleSupp
 };
 
 exported_symbols.testKeysIteratorNextIsCallable = async function() {
   const root = await navigator.storage.getDirectory();
 
   const it = await root.keys();
   Assert.ok(!!it, "Does root support keys iterator?");
 
-  try {
-    await it.next();
-    Assert.ok(false, "Should have thrown");
-  } catch (ex) {
-    Assert.ok(true, "Should have thrown");
-    Assert.equal(
-      ex.result,
-      Cr.NS_ERROR_NOT_IMPLEMENTED,
-      "Threw the right result code"
-    );
-  }
+  const item = await it.next();
+  Assert.ok(!!item, "Should return an item");
 };
 
 exported_symbols.testDirectoryHandleSupportsValuesIterator = async function() {
   const root = await navigator.storage.getDirectory();
 
   const it = await root.values();
   Assert.ok(!!it, "Does root support values iterator?");
 };
 
 exported_symbols.testValuesIteratorNextIsCallable = async function() {
   const root = await navigator.storage.getDirectory();
 
   const it = await root.values();
   Assert.ok(!!it, "Does root support values iterator?");
 
-  try {
-    await it.next();
-    Assert.ok(false, "Should have thrown");
-  } catch (ex) {
-    Assert.ok(true, "Should have thrown");
-    Assert.equal(
-      ex.result,
-      Cr.NS_ERROR_NOT_IMPLEMENTED,
-      "Threw the right result code"
-    );
-  }
+  const item = await it.next();
+  Assert.ok(!!item, "Should return an item");
 };
 
 exported_symbols.testDirectoryHandleSupportsEntriesIterator = async function() {
   const root = await navigator.storage.getDirectory();
 
   const it = await root.entries();
   Assert.ok(!!it, "Does root support entries iterator?");
 };
 
 exported_symbols.testEntriesIteratorNextIsCallable = async function() {
   const root = await navigator.storage.getDirectory();
 
   const it = await root.entries();
   Assert.ok(!!it, "Does root support entries iterator?");
 
-  try {
-    await it.next();
-    Assert.ok(false, "Should have thrown");
-  } catch (ex) {
-    Assert.ok(true, "Should have thrown");
-    Assert.equal(
-      ex.result,
-      Cr.NS_ERROR_NOT_IMPLEMENTED,
-      "Threw the right result code"
-    );
-  }
+  const item = await it.next();
+  Assert.ok(!!item, "Should return an item");
 };
 
 exported_symbols.testGetFileHandleIsCallable = async function() {
   const root = await navigator.storage.getDirectory();
   const allowCreate = { create: true };
 
-  try {
-    await root.getFileHandle("name", allowCreate);
-
-    Assert.ok(false, "Should have thrown");
-  } catch (ex) {
-    Assert.ok(true, "Should have thrown");
-    Assert.equal(
-      ex.result,
-      Cr.NS_ERROR_NOT_IMPLEMENTED,
-      "Threw the right result code"
-    );
-  }
+  const item = await root.getFileHandle("fileName", allowCreate);
+  Assert.ok(!!item, "Should return an item");
 };
 
 exported_symbols.testGetDirectoryHandleIsCallable = async function() {
   const root = await navigator.storage.getDirectory();
   const allowCreate = { create: true };
 
-  try {
-    await root.getDirectoryHandle("name", allowCreate);
-
-    Assert.ok(false, "Should have thrown");
-  } catch (ex) {
-    Assert.ok(true, "Should have thrown");
-    Assert.equal(
-      ex.result,
-      Cr.NS_ERROR_NOT_IMPLEMENTED,
-      "Threw the right result code"
-    );
-  }
+  const item = await root.getDirectoryHandle("dirName", allowCreate);
+  Assert.ok(!!item, "Should return an item");
 };
 
 exported_symbols.testRemoveEntryIsCallable = async function() {
   const root = await navigator.storage.getDirectory();
   const removeOptions = { recursive: true };
 
+  await root.removeEntry("fileName", removeOptions);
+  await root.removeEntry("dirName", removeOptions);
   try {
-    await root.removeEntry("root", removeOptions);
-
+    await root.removeEntry("doesNotExist", removeOptions);
     Assert.ok(false, "Should have thrown");
   } catch (ex) {
     Assert.ok(true, "Should have thrown");
     Assert.equal(
-      ex.result,
-      Cr.NS_ERROR_NOT_IMPLEMENTED,
-      "Threw the right result code"
+      ex.message,
+      "Unknown failure",
+      "Threw the right error message"
     );
   }
 };
 
 exported_symbols.testResolveIsCallable = async function() {
   const root = await navigator.storage.getDirectory();
 
   try {
--- a/dom/fs/test/common/test_fileSystemDirectoryHandle.js
+++ b/dom/fs/test/common/test_fileSystemDirectoryHandle.js
@@ -1,121 +1,96 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 exported_symbols.smokeTest = async function smokeTest() {
-  const step = async aIter => {
-    return aIter.next().catch(err => {
-      /* console.log(err.message); */
-    });
-  };
-
   const storage = navigator.storage;
   const subdirectoryNames = new Set(["Documents", "Downloads", "Music"]);
   const allowCreate = { create: true };
 
   {
     let root = await storage.getDirectory();
     Assert.ok(root, "Can we access the root directory?");
 
-    let it = root.values();
+    let it = await root.values();
     Assert.ok(!!it, "Does root have values iterator?");
 
-    let elem = await step(it);
-    Assert.ok(!elem, "Is root directory empty?");
+    let elem = await it.next();
+    Assert.ok(elem.done, "Is root directory empty?");
 
     for (let dirName of subdirectoryNames) {
-      try {
-        await root.getDirectoryHandle(dirName, allowCreate);
-      } catch (err) {
-        Assert.equal(
-          Cr.NS_ERROR_NOT_IMPLEMENTED,
-          err.result,
-          "Iterator not implemented yet"
-        );
+      await root.getDirectoryHandle(dirName, allowCreate);
+      Assert.ok(true, "Was it possible to add subdirectory " + dirName + "?");
+    }
+  }
+
+  {
+    let root = await storage.getDirectory();
+    Assert.ok(root, "Can we refresh the root directory?");
+
+    let it = await root.values();
+    Assert.ok(!!it, "Does root have values iterator?");
+
+    let hasElements = false;
+    let hangGuard = 0;
+    for await (let [key, elem] of root.entries()) {
+      Assert.ok(elem, "Is element not non-empty?");
+      Assert.equal("directory", elem.kind, "Is found item a directory?");
+      Assert.ok(
+        elem.name.length >= 1 && elem.name.match("^[A-Za-z]{1,64}"),
+        "Are names of the elements strings?"
+      );
+      Assert.equal(key, elem.name);
+      Assert.ok(subdirectoryNames.has(elem.name), "Is name among known names?");
+      hasElements = true;
+      ++hangGuard;
+      if (hangGuard == 10) {
+        break; // Exit if there is a hang
       }
-      // TODO: Implement iterator
-      // Assert.ok(true, "Was it possible to add subdirectory " + dirName + "?");
+    }
+
+    Assert.ok(hasElements, "Is values container now non-empty?");
+    Assert.equal(3, hangGuard, "Do we only have three elements?");
+
+    {
+      it = await root.values();
+      Assert.ok(!!it, "Does root have values iterator?");
+      let elem = await it.next();
+
+      await elem.value.getDirectoryHandle("Trash", allowCreate);
+      let subit = elem.value.values();
+      Assert.ok(!!elem, "Is element not non-empty?");
+      let subdirResult = await subit.next();
+      let subdir = subdirResult.value;
+      Assert.ok(!!subdir, "Is element not non-empty?");
+      Assert.equal("directory", subdir.kind, "Is found item a directory?");
+      Assert.equal("Trash", subdir.name, "Is found item a directory?");
+    }
+
+    const wipeEverything = { recursive: true };
+    for (let dirName of subdirectoryNames) {
+      await root.removeEntry(dirName, wipeEverything);
+      Assert.ok(
+        true,
+        "Was it possible to remove subdirectory " + dirName + "?"
+      );
     }
   }
 
   {
     let root = await storage.getDirectory();
     Assert.ok(root, "Can we refresh the root directory?");
 
     let it = root.values();
     Assert.ok(!!it, "Does root have values iterator?");
 
-    let hasElements = false;
-    let hangGuard = 0;
-    for (let elem = await step(it); elem; elem = await step(it)) {
-      Assert.ok(!!elem, "Is element not non-empty?");
-      Assert.equal("directory", elem.kind, "Is found item a directory?");
-      Assert.ok(
-        elem.name.length >= 1 && elem.name.match("^[A-Za-z]{1,64}"),
-        "Are names of the elements strings?"
-      );
-      Assert.ok(subdirectoryNames.has(elem.name), "Is name among known names?");
-      hasElements = true;
-      ++hangGuard;
-      if (hangGuard == 10) {
-        break; // Exit if there is a hang
-      }
-    }
-
-    Assert.ok(!hasElements, "Iterator not implemented yet");
-    // TODO: Implement iterator
-    // Assert.ok(hasElements, "Is values container now non-empty?");
-    // Assert.equal(3, hangGuard, "Do we only have three elements?");
-
-    {
-      it = root.values();
-      Assert.ok(!!it, "Does root have values iterator?");
-      let elem = await step(it);
-      Assert.ok(!elem, "Iterator not yet implemented");
-      // TODO: Implement iterator
-      // Assert.ok(!!elem, "Is element not non-empty?");
-      // await elem.getDirectoryHandle("Trash", allowCreate);
-      // let subit = elem.values();
-      // let subdir = await step(subit);
-      // Assert.ok(!!subdir, "Is element not non-empty?");
-      // Assert.equal("directory", subdir.kind, "Is found item a directory?");
-      // Assert.equal("Trash", subdir.name, "Is found item a directory?");
-    }
-
-    const wipeEverything = { recursive: true };
-    for (let dirName of subdirectoryNames) {
-      try {
-        await root.removeEntry(dirName, wipeEverything);
-      } catch (err) {
-        Assert.equal(
-          Cr.NS_ERROR_NOT_IMPLEMENTED,
-          err.result,
-          "Iterator not implemented yet"
-        );
-      }
-      // TODO: Implement iterator
-      // Assert.ok(
-      //   true,
-      //   "Was it possible to remove subdirectory " + dirName + "?"
-      // );
-    }
-  }
-
-  {
-    let root = await storage.getDirectory();
-    Assert.ok(root, "Can we refresh the root directory?");
-
-    let it = root.values();
-    Assert.ok(!!it, "Does root have values iterator?");
-
-    let elem = await step(it);
-    Assert.ok(!elem, "Is root directory empty?");
+    let elem = await it.next();
+    Assert.ok(elem.done, "Is root directory empty?");
   }
 };
 
 for (const [key, value] of Object.entries(exported_symbols)) {
   Object.defineProperty(value, "name", {
     value: key,
     writable: false,
   });
--- a/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp
+++ b/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp
@@ -28,74 +28,108 @@ class TestFileSystemDirectoryHandle : pu
   void SetUp() override {
     mRequestHandler = MakeUnique<MockFileSystemRequestHandler>();
     mMetadata = FileSystemEntryMetadata("dir"_ns, u"Directory"_ns,
                                         /* directory */ true);
     mName = u"testDir"_ns;
     mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr);
   }
 
+  FileSystemDirectoryHandle::iterator_t::WrapFunc GetWrapFunc() const {
+    return [](JSContext*, AsyncIterableIterator<FileSystemDirectoryHandle>*,
+              JS::Handle<JSObject*>,
+              JS::MutableHandle<JSObject*>) -> bool { return true; };
+  }
+
   nsIGlobalObject* mGlobal = GetGlobal();
+  const IterableIteratorBase::IteratorType mIteratorType =
+      IterableIteratorBase::IteratorType::Keys;
   UniquePtr<MockFileSystemRequestHandler> mRequestHandler;
   FileSystemEntryMetadata mMetadata;
   nsString mName;
   RefPtr<FileSystemManager> mManager;
 };
 
 TEST_F(TestFileSystemDirectoryHandle, constructDirectoryHandleRefPointer) {
   RefPtr<FileSystemDirectoryHandle> dirHandle =
       MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata);
 
   ASSERT_TRUE(dirHandle);
 }
 
-TEST_F(TestFileSystemDirectoryHandle, areEntriesReturned) {
+TEST_F(TestFileSystemDirectoryHandle, initAndDestroyIterator) {
   RefPtr<FileSystemDirectoryHandle> dirHandle =
       MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata,
                                                mRequestHandler.release());
 
   ASSERT_TRUE(dirHandle);
 
-  RefPtr<FileSystemDirectoryIterator> entries = dirHandle->Entries();
-  ASSERT_TRUE(entries);
+  RefPtr<FileSystemDirectoryHandle::iterator_t> iterator =
+      new FileSystemDirectoryHandle::iterator_t(dirHandle.get(), mIteratorType,
+                                                GetWrapFunc());
+  IgnoredErrorResult rv;
+  dirHandle->InitAsyncIterator(iterator, rv);
+  ASSERT_TRUE(iterator->GetData());
+
+  dirHandle->DestroyAsyncIterator(iterator);
+  ASSERT_EQ(nullptr, iterator->GetData());
 }
 
-TEST_F(TestFileSystemDirectoryHandle, areKeysReturned) {
+class MockFileSystemDirectoryIteratorImpl final
+    : public FileSystemDirectoryIterator::Impl {
+ public:
+  NS_INLINE_DECL_REFCOUNTING(MockFileSystemDirectoryIteratorImpl)
+
+  MOCK_METHOD(already_AddRefed<Promise>, Next,
+              (nsIGlobalObject * aGlobal, RefPtr<FileSystemManager>& aManager,
+               ErrorResult& aError),
+              (override));
+
+ protected:
+  ~MockFileSystemDirectoryIteratorImpl() = default;
+};
+
+TEST_F(TestFileSystemDirectoryHandle, isNextPromiseReturned) {
   RefPtr<FileSystemDirectoryHandle> dirHandle =
       MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata,
                                                mRequestHandler.release());
 
   ASSERT_TRUE(dirHandle);
 
-  RefPtr<FileSystemDirectoryIterator> keys = dirHandle->Keys();
-  ASSERT_TRUE(keys);
-}
+  auto* mockIter = new MockFileSystemDirectoryIteratorImpl();
+  IgnoredErrorResult error;
+  EXPECT_CALL(*mockIter, Next(_, _, _))
+      .WillOnce(::testing::Return(Promise::Create(mGlobal, error)));
 
-TEST_F(TestFileSystemDirectoryHandle, areValuesReturned) {
-  RefPtr<FileSystemDirectoryHandle> dirHandle =
-      MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata,
-                                               mRequestHandler.release());
+  RefPtr<FileSystemDirectoryHandle::iterator_t> iterator =
+      MakeAndAddRef<FileSystemDirectoryHandle::iterator_t>(
+          dirHandle.get(), mIteratorType, GetWrapFunc());
+  iterator->SetData(static_cast<void*>(mockIter));
 
-  ASSERT_TRUE(dirHandle);
+  IgnoredErrorResult rv;
+  RefPtr<Promise> promise =
+      dirHandle->GetNextPromise(nullptr, iterator.get(), rv);
+  ASSERT_TRUE(promise);
 
-  RefPtr<FileSystemDirectoryIterator> values = dirHandle->Values();
-  ASSERT_TRUE(values);
+  dirHandle->DestroyAsyncIterator(iterator.get());
 }
 
 TEST_F(TestFileSystemDirectoryHandle, isHandleKindDirectory) {
   RefPtr<FileSystemDirectoryHandle> dirHandle =
       MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata,
                                                mRequestHandler.release());
 
   ASSERT_TRUE(dirHandle);
 
   ASSERT_EQ(FileSystemHandleKind::Directory, dirHandle->Kind());
 }
 
 TEST_F(TestFileSystemDirectoryHandle, isFileHandleReturned) {
+  EXPECT_CALL(*mRequestHandler, GetFileHandle(_, _, _, _))
+      .WillOnce(::testing::ReturnArg<3>());
   RefPtr<FileSystemDirectoryHandle> dirHandle =
       MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata,
                                                mRequestHandler.release());
 
   ASSERT_TRUE(dirHandle);
 
   FileSystemGetFileOptions options;
   IgnoredErrorResult rv;
@@ -114,16 +148,18 @@ TEST_F(TestFileSystemDirectoryHandle, do
   FileSystemGetFileOptions options;
   IgnoredErrorResult rv;
   RefPtr<Promise> promise = dirHandle->GetFileHandle(mName, options, rv);
 
   ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED));
 }
 
 TEST_F(TestFileSystemDirectoryHandle, isDirectoryHandleReturned) {
+  EXPECT_CALL(*mRequestHandler, GetDirectoryHandle(_, _, _, _))
+      .WillOnce(::testing::ReturnArg<3>());
   RefPtr<FileSystemDirectoryHandle> dirHandle =
       MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata,
                                                mRequestHandler.release());
 
   ASSERT_TRUE(dirHandle);
 
   FileSystemGetDirectoryOptions options;
   IgnoredErrorResult rv;
@@ -142,16 +178,18 @@ TEST_F(TestFileSystemDirectoryHandle, do
   FileSystemGetDirectoryOptions options;
   IgnoredErrorResult rv;
   RefPtr<Promise> promise = dirHandle->GetDirectoryHandle(mName, options, rv);
 
   ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED));
 }
 
 TEST_F(TestFileSystemDirectoryHandle, isRemoveEntrySuccessful) {
+  EXPECT_CALL(*mRequestHandler, RemoveEntry(_, _, _, _))
+      .WillOnce(::testing::ReturnArg<3>());
   RefPtr<FileSystemDirectoryHandle> dirHandle =
       MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata,
                                                mRequestHandler.release());
 
   ASSERT_TRUE(dirHandle);
 
   FileSystemRemoveOptions options;
   IgnoredErrorResult rv;
--- a/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp
+++ b/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp
@@ -1,20 +1,19 @@
 /* -*- 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 "gtest/gtest.h"
-
+#include "ArrayAppendable.h"
 #include "FileSystemBackgroundRequestHandler.h"
 #include "FileSystemMocks.h"
 #include "fs/FileSystemRequestHandler.h"
-
+#include "gtest/gtest.h"
 #include "mozilla/dom/IPCBlob.h"
 #include "mozilla/dom/FileSystemManager.h"
 #include "mozilla/dom/FileSystemManagerChild.h"
 #include "mozilla/dom/PFileSystemManager.h"
 #include "mozilla/dom/StorageManager.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/IPCCore.h"
 #include "mozilla/SpinEventLoopUntil.h"
@@ -169,16 +168,24 @@ TEST_F(TestFileSystemRequestHandler, isG
 
   RefPtr<Promise> promise = GetDefaultPromise();
   auto testable = GetFileSystemRequestHandler();
   testable->GetFile(mManager, mEntry, promise);
   SpinEventLoopUntil("Promise is fulfilled or timeout"_ns,
                      [this]() { return mListener->IsDone(); });
 }
 
+class TestArrayAppendable : public ArrayAppendable {
+ public:
+  MOCK_METHOD(void, append,
+              (nsIGlobalObject * aGlobal, RefPtr<FileSystemManager>& aManager,
+               const nsTArray<FileSystemEntryMetadata>&),
+              (override));
+};
+
 TEST_F(TestFileSystemRequestHandler, isGetEntriesSuccessful) {
   auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve,
                          auto&& /* aReject */) {
     nsTArray<FileSystemEntryMetadata> files;
     nsTArray<FileSystemEntryMetadata> directories;
     FileSystemDirectoryListing listing(files, directories);
     FileSystemGetEntriesResponse response(listing);
     aResolve(std::move(response));
@@ -195,17 +202,19 @@ TEST_F(TestFileSystemRequestHandler, isG
   EXPECT_CALL(*mFileSystemManagerChild, Shutdown())
       .WillOnce([fileSystemManagerChild =
                      static_cast<void*>(mFileSystemManagerChild.get())]() {
         static_cast<TestFileSystemManagerChild*>(fileSystemManagerChild)
             ->FileSystemManagerChild::Shutdown();
       });
 
   auto testable = GetFileSystemRequestHandler();
-  ArrayAppendable sink;
+  TestArrayAppendable sink;
+  EXPECT_CALL(sink, append(_, _, _)).Times(1);
+
   testable->GetEntries(mManager, mEntry.entryId(), /* page */ 0, promise, sink);
   SpinEventLoopUntil("Promise is fulfilled or timeout"_ns,
                      [listener]() { return listener->IsDone(); });
 }
 
 TEST_F(TestFileSystemRequestHandler, isRemoveEntrySuccessful) {
   auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve,
                          auto&& /* aReject */) {
--- a/dom/quota/StorageManager.cpp
+++ b/dom/quota/StorageManager.cpp
@@ -1,15 +1,16 @@
 /* -*- 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 "StorageManager.h"
+#include "fs/FileSystemRequestHandler.h"
 
 #include <cstdint>
 #include <cstdlib>
 #include <utility>
 #include "ErrorList.h"
 #include "fs/FileSystemRequestHandler.h"
 #include "MainThreadUtils.h"
 #include "js/CallArgs.h"
--- a/dom/webidl/FileSystemDirectoryHandle.webidl
+++ b/dom/webidl/FileSystemDirectoryHandle.webidl
@@ -10,29 +10,25 @@ dictionary FileSystemGetFileOptions {
 dictionary FileSystemGetDirectoryOptions {
   boolean create = false;
 };
 
 dictionary FileSystemRemoveOptions {
   boolean recursive = false;
 };
 
-// TODO: Add Serializzable
+// TODO: Add Serializable
 [Exposed=(Window,Worker), SecureContext, Pref="dom.fs.enabled"]
 interface FileSystemDirectoryHandle : FileSystemHandle {
-  // This interface defines an async iterable, however that isn't supported yet
-  // by the bindings. So for now just explicitly define what an async iterable
-  // definition implies.
-  //async iterable<USVString, FileSystemHandle>;
-  FileSystemDirectoryIterator entries();
-  FileSystemDirectoryIterator keys();
-  FileSystemDirectoryIterator values();
+
+  async iterable<USVString, FileSystemHandle>;
 
   [NewObject]
   Promise<FileSystemFileHandle> getFileHandle(USVString name, optional FileSystemGetFileOptions options = {});
+
   [NewObject]
   Promise<FileSystemDirectoryHandle> getDirectoryHandle(USVString name, optional FileSystemGetDirectoryOptions options = {});
 
   [NewObject]
   Promise<void> removeEntry(USVString name, optional FileSystemRemoveOptions options = {});
 
   [NewObject]
   Promise<sequence<USVString>?> resolve(FileSystemHandle possibleDescendant);