Bug 1258694 - Implement Directory::GetFiles(), r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 13 Apr 2016 07:15:56 -0400
changeset 330882 bd1aca23aba6cc93019b9756f27b8079132f627f
parent 330881 9db1fc7d3df35bb343bc8d83231737b0c900bc63
child 330883 aceaeee7103fca35df23559f00a2dd8c56b1f285
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1258694
milestone48.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 1258694 - Implement Directory::GetFiles(), r=smaug
dom/filesystem/CreateDirectoryTask.cpp
dom/filesystem/CreateDirectoryTask.h
dom/filesystem/CreateFileTask.cpp
dom/filesystem/Directory.cpp
dom/filesystem/Directory.h
dom/filesystem/FileSystemRequestParent.cpp
dom/filesystem/FileSystemTaskBase.h
dom/filesystem/GetDirectoryListingTask.cpp
dom/filesystem/GetFileOrDirectoryTask.cpp
dom/filesystem/GetFilesTask.cpp
dom/filesystem/GetFilesTask.h
dom/filesystem/PFileSystemParams.ipdlh
dom/filesystem/PFileSystemRequest.ipdl
dom/filesystem/RemoveTask.cpp
dom/filesystem/RemoveTask.h
dom/filesystem/moz.build
dom/filesystem/tests/filesystem_commons.js
dom/filesystem/tests/mochitest.ini
dom/filesystem/tests/script_fileList.js
dom/filesystem/tests/test_basic.html
dom/filesystem/tests/worker_basic.js
dom/webidl/Directory.webidl
--- a/dom/filesystem/CreateDirectoryTask.cpp
+++ b/dom/filesystem/CreateDirectoryTask.cpp
@@ -126,17 +126,17 @@ CreateDirectoryTaskChild::HandlerCallbac
 
   mPromise->MaybeResolve(dir);
   mPromise = nullptr;
 }
 
 void
 CreateDirectoryTaskChild::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(CREATE_DIRECTORY_TASK_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_CREATE_PERMISSION);
 }
 
 /**
  * CreateDirectoryTaskParent
  */
 
 /* static */ already_AddRefed<CreateDirectoryTaskParent>
 CreateDirectoryTaskParent::Create(FileSystemBase* aFileSystem,
@@ -211,13 +211,13 @@ CreateDirectoryTaskParent::IOWork()
   }
 
   return NS_OK;
 }
 
 void
 CreateDirectoryTaskParent::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(CREATE_DIRECTORY_TASK_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_CREATE_PERMISSION);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/filesystem/CreateDirectoryTask.h
+++ b/dom/filesystem/CreateDirectoryTask.h
@@ -6,18 +6,16 @@
 
 #ifndef mozilla_dom_CreateDirectoryTask_h
 #define mozilla_dom_CreateDirectoryTask_h
 
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ErrorResult.h"
 
-#define CREATE_DIRECTORY_TASK_PERMISSION "create"
-
 namespace mozilla {
 namespace dom {
 
 class FileSystemCreateDirectoryParams;
 class Promise;
 
 class CreateDirectoryTaskChild final : public FileSystemTaskChildBase
 {
--- a/dom/filesystem/CreateFileTask.cpp
+++ b/dom/filesystem/CreateFileTask.cpp
@@ -1,17 +1,15 @@
 /* -*- 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 "CreateFileTask.h"
-#include "CreateDirectoryTask.h"
-#include "RemoveTask.h"
 
 #include <algorithm>
 
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileSystemBase.h"
 #include "mozilla/dom/FileSystemUtils.h"
 #include "mozilla/dom/PFileSystemParams.h"
@@ -22,20 +20,20 @@
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "nsIFile.h"
 #include "nsNetUtil.h"
 #include "nsIOutputStream.h"
 #include "nsStringGlue.h"
 
 #define GET_PERMISSION_ACCESS_TYPE(aAccess)                \
   if (mReplace) {                                          \
-    aAccess.AssignLiteral(REMOVE_TASK_PERMISSION);         \
+    aAccess.AssignLiteral(DIRECTORY_WRITE_PERMISSION);     \
     return;                                                \
   }                                                        \
-  aAccess.AssignLiteral(CREATE_DIRECTORY_TASK_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_CREATE_PERMISSION);
 
 namespace mozilla {
 namespace dom {
 
 /**
  *CreateFileTaskChild
  */
 
--- a/dom/filesystem/Directory.cpp
+++ b/dom/filesystem/Directory.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/dom/Directory.h"
 
 #include "CreateDirectoryTask.h"
 #include "CreateFileTask.h"
 #include "FileSystemPermissionRequest.h"
 #include "GetDirectoryListingTask.h"
 #include "GetFileOrDirectoryTask.h"
+#include "GetFilesTask.h"
 #include "RemoveTask.h"
 
 #include "nsCharSeparatedTokenizer.h"
 #include "nsString.h"
 #include "mozilla/dom/DirectoryBinding.h"
 #include "mozilla/dom/FileSystemBase.h"
 #include "mozilla/dom/FileSystemUtils.h"
 #include "mozilla/dom/OSFileSystem.h"
@@ -441,16 +442,37 @@ Directory::GetFilesAndDirectories(ErrorR
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   FileSystemPermissionRequest::RequestForTask(task);
   return task->GetPromise();
 }
 
+already_AddRefed<Promise>
+Directory::GetFiles(bool aRecursiveFlag, ErrorResult& aRv)
+{
+  ErrorResult rv;
+  RefPtr<FileSystemBase> fs = GetFileSystem(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  RefPtr<GetFilesTaskChild> task =
+    GetFilesTaskChild::Create(fs, mFile, aRecursiveFlag, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  FileSystemPermissionRequest::RequestForTask(task);
+  return task->GetPromise();
+}
+
 void
 Directory::SetContentFilters(const nsAString& aFilters)
 {
   mFilters = aFilters;
 }
 
 FileSystemBase*
 Directory::GetFileSystem(ErrorResult& aRv)
--- a/dom/filesystem/Directory.h
+++ b/dom/filesystem/Directory.h
@@ -107,16 +107,19 @@ public:
   GetPath(nsAString& aRetval, ErrorResult& aRv);
 
   nsresult
   GetFullRealPath(nsAString& aPath);
 
   already_AddRefed<Promise>
   GetFilesAndDirectories(ErrorResult& aRv);
 
+  already_AddRefed<Promise>
+  GetFiles(bool aRecursiveFlag, ErrorResult& aRv);
+
   // =========== End WebIDL bindings.============
 
   /**
    * Sets a semi-colon separated list of filters to filter-in or filter-out
    * certain types of files when the contents of this directory are requested
    * via a GetFilesAndDirectories() call.
    *
    * Currently supported keywords:
--- a/dom/filesystem/FileSystemRequestParent.cpp
+++ b/dom/filesystem/FileSystemRequestParent.cpp
@@ -48,16 +48,17 @@ FileSystemRequestParent::Initialize(cons
   ErrorResult rv;
 
   switch (aParams.type()) {
 
     FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(CreateDirectory)
     FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(CreateFile)
     FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetDirectoryListing)
     FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFileOrDirectory)
+    FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFiles)
     FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(Remove)
 
     default: {
       NS_RUNTIMEABORT("not reached");
       break;
     }
   }
 
--- a/dom/filesystem/FileSystemTaskBase.h
+++ b/dom/filesystem/FileSystemTaskBase.h
@@ -15,16 +15,20 @@
 namespace mozilla {
 namespace dom {
 
 class BlobImpl;
 class FileSystemBase;
 class FileSystemParams;
 class PBlobParent;
 
+#define DIRECTORY_READ_PERMISSION "read"
+#define DIRECTORY_WRITE_PERMISSION "write"
+#define DIRECTORY_CREATE_PERMISSION "create"
+
 /*
  * The base class to implement a Task class.
  * The file system operations can only be performed in the parent process. In
  * order to avoid duplicated code, we used PBackground for child-parent and
  * parent-parent communications.
  *
  * The following diagram illustrates the how a API call from the content page
  * starts a task and gets call back results.
--- a/dom/filesystem/GetDirectoryListingTask.cpp
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -13,18 +13,16 @@
 #include "mozilla/dom/FileSystemUtils.h"
 #include "mozilla/dom/PFileSystemParams.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "nsIFile.h"
 #include "nsStringGlue.h"
 
-#define GET_DIRECTORY_LISTING_PERMISSION "read"
-
 namespace mozilla {
 namespace dom {
 
 /**
  * GetDirectoryListingTaskChild
  */
 
 /* static */ already_AddRefed<GetDirectoryListingTaskChild>
@@ -201,17 +199,17 @@ GetDirectoryListingTaskChild::HandlerCal
 
   mPromise->MaybeResolve(listing);
   mPromise = nullptr;
 }
 
 void
 GetDirectoryListingTaskChild::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(GET_DIRECTORY_LISTING_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_READ_PERMISSION);
 }
 
 /**
  * GetDirectoryListingTaskParent
  */
 
 /* static */ already_AddRefed<GetDirectoryListingTaskParent>
 GetDirectoryListingTaskParent::Create(FileSystemBase* aFileSystem,
@@ -389,13 +387,13 @@ GetDirectoryListingTaskParent::IOWork()
     }
   }
   return NS_OK;
 }
 
 void
 GetDirectoryListingTaskParent::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(GET_DIRECTORY_LISTING_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_READ_PERMISSION);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/filesystem/GetFileOrDirectoryTask.cpp
+++ b/dom/filesystem/GetFileOrDirectoryTask.cpp
@@ -12,18 +12,16 @@
 #include "mozilla/dom/FileSystemUtils.h"
 #include "mozilla/dom/PFileSystemParams.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "nsIFile.h"
 #include "nsStringGlue.h"
 
-#define GET_FILE_OR_DIRECTORY_PERMISSION "read"
-
 namespace mozilla {
 namespace dom {
 
 /**
  * GetFileOrDirectoryTaskChild
  */
 
 /* static */ already_AddRefed<GetFileOrDirectoryTaskChild>
@@ -166,17 +164,17 @@ GetFileOrDirectoryTaskChild::HandlerCall
                                            mTargetPath);
   mPromise->MaybeResolve(file);
   mPromise = nullptr;
 }
 
 void
 GetFileOrDirectoryTaskChild::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(GET_FILE_OR_DIRECTORY_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_READ_PERMISSION);
 }
 
 /**
  * GetFileOrDirectoryTaskParent
  */
 
 /* static */ already_AddRefed<GetFileOrDirectoryTaskParent>
 GetFileOrDirectoryTaskParent::Create(FileSystemBase* aFileSystem,
@@ -293,13 +291,13 @@ GetFileOrDirectoryTaskParent::IOWork()
   }
 
   return NS_OK;
 }
 
 void
 GetFileOrDirectoryTaskParent::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(GET_FILE_OR_DIRECTORY_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_READ_PERMISSION);
 }
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFilesTask.cpp
@@ -0,0 +1,359 @@
+/* -*- 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 "GetFilesTask.h"
+
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "js/Value.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/PFileSystemParams.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "nsIFile.h"
+#include "nsStringGlue.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * GetFilesTaskChild
+ */
+
+/* static */ already_AddRefed<GetFilesTaskChild>
+GetFilesTaskChild::Create(FileSystemBase* aFileSystem,
+                          nsIFile* aTargetPath,
+                          bool aRecursiveFlag,
+                          ErrorResult& aRv)
+{
+  MOZ_ASSERT(aFileSystem);
+  aFileSystem->AssertIsOnOwningThread();
+
+  nsCOMPtr<nsIGlobalObject> globalObject =
+    do_QueryInterface(aFileSystem->GetParentObject());
+  if (NS_WARN_IF(!globalObject)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  RefPtr<GetFilesTaskChild> task =
+    new GetFilesTaskChild(aFileSystem, aTargetPath, aRecursiveFlag);
+
+  // aTargetPath can be null. In this case SetError will be called.
+
+  task->mPromise = Promise::Create(globalObject, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return task.forget();
+}
+
+GetFilesTaskChild::GetFilesTaskChild(FileSystemBase* aFileSystem,
+                                     nsIFile* aTargetPath,
+                                     bool aRecursiveFlag)
+  : FileSystemTaskChildBase(aFileSystem)
+  , mTargetPath(aTargetPath)
+  , mRecursiveFlag(aRecursiveFlag)
+{
+  MOZ_ASSERT(aFileSystem);
+  aFileSystem->AssertIsOnOwningThread();
+}
+
+GetFilesTaskChild::~GetFilesTaskChild()
+{
+  mFileSystem->AssertIsOnOwningThread();
+}
+
+already_AddRefed<Promise>
+GetFilesTaskChild::GetPromise()
+{
+  mFileSystem->AssertIsOnOwningThread();
+  return RefPtr<Promise>(mPromise).forget();
+}
+
+FileSystemParams
+GetFilesTaskChild::GetRequestParams(const nsString& aSerializedDOMPath,
+                                    ErrorResult& aRv) const
+{
+  mFileSystem->AssertIsOnOwningThread();
+
+  nsAutoString path;
+  aRv = mTargetPath->GetPath(path);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return FileSystemGetFilesParams();
+  }
+
+  return FileSystemGetFilesParams(aSerializedDOMPath, path, mRecursiveFlag);
+}
+
+void
+GetFilesTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue,
+                                           ErrorResult& aRv)
+{
+  mFileSystem->AssertIsOnOwningThread();
+  MOZ_ASSERT(aValue.type() ==
+               FileSystemResponseValue::TFileSystemFilesResponse);
+
+  FileSystemFilesResponse r = aValue;
+
+  if (!mTargetData.SetLength(r.data().Length(), mozilla::fallible_t())) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  for (uint32_t i = 0; i < r.data().Length(); ++i) {
+    const FileSystemFileResponse& data = r.data()[i];
+    mTargetData[i] = data.realPath();
+  }
+}
+
+void
+GetFilesTaskChild::HandlerCallback()
+{
+  mFileSystem->AssertIsOnOwningThread();
+  if (mFileSystem->IsShutdown()) {
+    mPromise = nullptr;
+    return;
+  }
+
+  if (HasError()) {
+    mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    mPromise = nullptr;
+    return;
+  }
+
+  size_t count = mTargetData.Length();
+
+  Sequence<RefPtr<File>> listing;
+
+  if (!listing.SetLength(count, mozilla::fallible_t())) {
+    mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+    mPromise = nullptr;
+    return;
+  }
+
+  for (unsigned i = 0; i < count; i++) {
+    nsCOMPtr<nsIFile> path;
+    NS_ConvertUTF16toUTF8 fullPath(mTargetData[i]);
+    nsresult rv = NS_NewNativeLocalFile(fullPath, true, getter_AddRefs(path));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+      mPromise = nullptr;
+      return;
+    }
+
+#ifdef DEBUG
+    nsCOMPtr<nsIFile> rootPath;
+    rv = NS_NewLocalFile(mFileSystem->LocalOrDeviceStorageRootPath(), false,
+                         getter_AddRefs(rootPath));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+      mPromise = nullptr;
+      return;
+    }
+
+    MOZ_ASSERT(FileSystemUtils::IsDescendantPath(rootPath, path));
+#endif
+
+    RefPtr<File> file =
+      File::CreateFromFile(mFileSystem->GetParentObject(), path);
+    MOZ_ASSERT(file);
+
+    listing[i] = file;
+  }
+
+  mPromise->MaybeResolve(listing);
+  mPromise = nullptr;
+}
+
+void
+GetFilesTaskChild::GetPermissionAccessType(nsCString& aAccess) const
+{
+  aAccess.AssignLiteral("read");
+}
+
+/**
+ * GetFilesTaskParent
+ */
+
+/* static */ already_AddRefed<GetFilesTaskParent>
+GetFilesTaskParent::Create(FileSystemBase* aFileSystem,
+                           const FileSystemGetFilesParams& aParam,
+                           FileSystemRequestParent* aParent,
+                           ErrorResult& aRv)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aFileSystem);
+
+  RefPtr<GetFilesTaskParent> task =
+    new GetFilesTaskParent(aFileSystem, aParam, aParent);
+
+  NS_ConvertUTF16toUTF8 path(aParam.realPath());
+  aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath));
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return task.forget();
+}
+
+GetFilesTaskParent::GetFilesTaskParent(FileSystemBase* aFileSystem,
+                                       const FileSystemGetFilesParams& aParam,
+                                       FileSystemRequestParent* aParent)
+  : FileSystemTaskParentBase(aFileSystem, aParam, aParent)
+  , mRecursiveFlag(aParam.recursiveFlag())
+{
+  MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aFileSystem);
+}
+
+FileSystemResponseValue
+GetFilesTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const
+{
+  AssertIsOnBackgroundThread();
+
+  InfallibleTArray<PBlobParent*> blobs;
+
+  FallibleTArray<FileSystemFileResponse> inputs;
+  if (!inputs.SetLength(mTargetData.Length(), mozilla::fallible_t())) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    FileSystemFilesResponse response;
+    return response;
+  }
+
+  for (unsigned i = 0; i < mTargetData.Length(); i++) {
+    FileSystemFileResponse fileData;
+    fileData.realPath() = mTargetData[i];
+    inputs[i] = fileData;
+  }
+
+  FileSystemFilesResponse response;
+  response.data().SwapElements(inputs);
+  return response;
+}
+
+nsresult
+GetFilesTaskParent::IOWork()
+{
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(!NS_IsMainThread(), "Only call on I/O thread!");
+
+  if (mFileSystem->IsShutdown()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  bool exists;
+  nsresult rv = mTargetPath->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!exists) {
+    return NS_OK;
+  }
+
+  // Get isDirectory.
+  rv = ExploreDirectory(mTargetPath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GetFilesTaskParent::ExploreDirectory(nsIFile* aPath)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!");
+  MOZ_ASSERT(aPath);
+
+  bool isDir;
+  nsresult rv = aPath->IsDirectory(&isDir);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!isDir) {
+    return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+  }
+
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  rv = aPath->GetDirectoryEntries(getter_AddRefs(entries));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  for (;;) {
+    bool hasMore = false;
+    if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
+      break;
+    }
+    nsCOMPtr<nsISupports> supp;
+    if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
+      break;
+    }
+
+    nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
+    MOZ_ASSERT(currFile);
+
+    bool isLink, isSpecial, isFile;
+    if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
+                   NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+        isLink || isSpecial) {
+      continue;
+    }
+
+    if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
+                   NS_FAILED(currFile->IsDirectory(&isDir))) ||
+        !(isFile || isDir)) {
+      continue;
+    }
+
+    if (isFile) {
+      nsAutoString path;
+      if (NS_WARN_IF(NS_FAILED(currFile->GetPath(path)))) {
+        continue;
+      }
+
+      if (!mTargetData.AppendElement(path, fallible)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+
+      continue;
+    }
+
+    MOZ_ASSERT(isDir);
+
+    if (!mRecursiveFlag) {
+      continue;
+    }
+
+    // Recursive.
+    rv = ExploreDirectory(currFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+GetFilesTaskParent::GetPermissionAccessType(nsCString& aAccess) const
+{
+  aAccess.AssignLiteral(DIRECTORY_READ_PERMISSION);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFilesTask.h
@@ -0,0 +1,99 @@
+/* -*- 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 mozilla_dom_GetFilesTask_h
+#define mozilla_dom_GetFilesTask_h
+
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemTaskBase.h"
+#include "mozilla/ErrorResult.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+
+class GetFilesTaskChild final : public FileSystemTaskChildBase
+{
+public:
+  static already_AddRefed<GetFilesTaskChild>
+  Create(FileSystemBase* aFileSystem,
+         nsIFile* aTargetPath,
+         bool aRecursiveFlag,
+         ErrorResult& aRv);
+
+  virtual
+  ~GetFilesTaskChild();
+
+  already_AddRefed<Promise>
+  GetPromise();
+
+  virtual void
+  GetPermissionAccessType(nsCString& aAccess) const override;
+
+private:
+  // If aDirectoryOnly is set, we should ensure that the target is a directory.
+  GetFilesTaskChild(FileSystemBase* aFileSystem,
+                    nsIFile* aTargetPath,
+                    bool aRecursiveFlag);
+
+  virtual FileSystemParams
+  GetRequestParams(const nsString& aSerializedDOMPath,
+                   ErrorResult& aRv) const override;
+
+  virtual void
+  SetSuccessRequestResult(const FileSystemResponseValue& aValue,
+                          ErrorResult& aRv) override;
+
+  virtual void
+  HandlerCallback() override;
+
+  RefPtr<Promise> mPromise;
+  nsCOMPtr<nsIFile> mTargetPath;
+  bool mRecursiveFlag;
+
+  // We store the fullpath of Files.
+  FallibleTArray<nsString> mTargetData;
+};
+
+class GetFilesTaskParent final : public FileSystemTaskParentBase
+{
+public:
+  static already_AddRefed<GetFilesTaskParent>
+  Create(FileSystemBase* aFileSystem,
+         const FileSystemGetFilesParams& aParam,
+         FileSystemRequestParent* aParent,
+         ErrorResult& aRv);
+
+  virtual void
+  GetPermissionAccessType(nsCString& aAccess) const override;
+
+private:
+  GetFilesTaskParent(FileSystemBase* aFileSystem,
+                     const FileSystemGetFilesParams& aParam,
+                     FileSystemRequestParent* aParent);
+
+  virtual FileSystemResponseValue
+  GetSuccessRequestResult(ErrorResult& aRv) const override;
+
+  virtual nsresult
+  IOWork() override;
+
+  nsresult
+  ExploreDirectory(nsIFile* aPath);
+
+  nsCOMPtr<nsIFile> mTargetPath;
+  bool mRecursiveFlag;
+
+  // We store the fullpath of Files.
+  FallibleTArray<nsString> mTargetData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GetFilesTask_h
--- a/dom/filesystem/PFileSystemParams.ipdlh
+++ b/dom/filesystem/PFileSystemParams.ipdlh
@@ -39,16 +39,23 @@ struct FileSystemGetDirectoryListingPara
   // filters.  Since an nsString can share its buffer when copied,
   // using that instead of InfallibleTArray<nsString> makes copying the filters
   // around in any given process a bit more efficient too, since copying a
   // single nsString is cheaper than copying InfallibleTArray member data and
   // each nsString that it contains.
   nsString filters;
 };
 
+struct FileSystemGetFilesParams
+{
+  nsString filesystem;
+  nsString realPath;
+  bool recursiveFlag;
+};
+
 struct FileSystemGetFileOrDirectoryParams
 {
   nsString filesystem;
   nsString realPath;
   bool isRoot;
 };
 
 struct FileSystemRemoveParams
@@ -59,14 +66,15 @@ struct FileSystemRemoveParams
   bool recursive;
 };
 
 union FileSystemParams
 {
   FileSystemCreateDirectoryParams;
   FileSystemCreateFileParams;
   FileSystemGetDirectoryListingParams;
+  FileSystemGetFilesParams;
   FileSystemGetFileOrDirectoryParams;
   FileSystemRemoveParams;
 };
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/filesystem/PFileSystemRequest.ipdl
+++ b/dom/filesystem/PFileSystemRequest.ipdl
@@ -38,32 +38,38 @@ union FileSystemDirectoryListingResponse
   FileSystemDirectoryListingResponseDirectory;
 };
 
 struct FileSystemDirectoryListingResponse
 {
   FileSystemDirectoryListingResponseData[] data;
 };
 
+struct FileSystemFilesResponse
+{
+  FileSystemFileResponse[] data;
+};
+
 struct FileSystemErrorResponse
 {
   nsresult error;
 };
 
 struct FileSystemBooleanResponse
 {
   bool success;
 };
 
 union FileSystemResponseValue
 {
   FileSystemBooleanResponse;
   FileSystemDirectoryResponse;
   FileSystemDirectoryListingResponse;
   FileSystemFileResponse;
+  FileSystemFilesResponse;
   FileSystemErrorResponse;
 };
 
 protocol PFileSystemRequest
 {
   manager PBackground;
 
 child:
--- a/dom/filesystem/RemoveTask.cpp
+++ b/dom/filesystem/RemoveTask.cpp
@@ -137,17 +137,17 @@ RemoveTaskChild::HandlerCallback()
 
   mPromise->MaybeResolve(mReturnValue);
   mPromise = nullptr;
 }
 
 void
 RemoveTaskChild::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(REMOVE_TASK_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_WRITE_PERMISSION);
 }
 
 /**
  * RemoveTaskParent
  */
 
 /* static */ already_AddRefed<RemoveTaskParent>
 RemoveTaskParent::Create(FileSystemBase* aFileSystem,
@@ -247,13 +247,13 @@ RemoveTaskParent::IOWork()
   mReturnValue = true;
 
   return NS_OK;
 }
 
 void
 RemoveTaskParent::GetPermissionAccessType(nsCString& aAccess) const
 {
-  aAccess.AssignLiteral(REMOVE_TASK_PERMISSION);
+  aAccess.AssignLiteral(DIRECTORY_WRITE_PERMISSION);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/filesystem/RemoveTask.h
+++ b/dom/filesystem/RemoveTask.h
@@ -6,18 +6,16 @@
 
 #ifndef mozilla_dom_RemoveTask_h
 #define mozilla_dom_RemoveTask_h
 
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ErrorResult.h"
 
-#define REMOVE_TASK_PERMISSION "write"
-
 namespace mozilla {
 namespace dom {
 
 class BlobImpl;
 class Promise;
 
 class RemoveTaskChild final : public FileSystemTaskChildBase
 {
--- a/dom/filesystem/moz.build
+++ b/dom/filesystem/moz.build
@@ -23,16 +23,17 @@ UNIFIED_SOURCES += [
     'Directory.cpp',
     'FileSystemBase.cpp',
     'FileSystemPermissionRequest.cpp',
     'FileSystemRequestParent.cpp',
     'FileSystemTaskBase.cpp',
     'FileSystemUtils.cpp',
     'GetDirectoryListingTask.cpp',
     'GetFileOrDirectoryTask.cpp',
+    'GetFilesTask.cpp',
     'OSFileSystem.cpp',
     'RemoveTask.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 IPDL_SOURCES += [
     'PFileSystemParams.ipdlh',
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/tests/filesystem_commons.js
@@ -0,0 +1,75 @@
+function test_basic(aDirectory, aNext) {
+  ok(aDirectory, "Directory exists.");
+  ok(aDirectory instanceof Directory, "We have a directory.");
+  is(aDirectory.name, '/', "directory.name must be '/'");
+  is(aDirectory.path, '/', "directory.path must be '/'");
+  aNext();
+}
+
+function test_getFilesAndDirectories(aDirectory, aRecursive, aNext) {
+  function checkSubDir(dir) {
+    return dir.getFilesAndDirectories().then(
+      function(data) {
+        for (var i = 0; i < data.length; ++i) {
+          ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
+          if (data[i] instanceof Directory) {
+            isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
+            isnot(data[i].path, '/', "Subdirectory path should be called with the leafname");
+            isnot(data[i].path, dir.path, "Subdirectory path should contain the parent path.");
+            is(data[i].path,dir.path + '/' + data[i].name, "Subdirectory path should be called parentdir.path + '/' + leafname");
+          }
+        }
+      }
+    );
+  }
+
+  aDirectory.getFilesAndDirectories().then(
+    function(data) {
+      ok(data.length, "We should have some data.");
+      var promises = [];
+      for (var i = 0; i < data.length; ++i) {
+        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories: " + data[i].name);
+        if (data[i] instanceof Directory) {
+          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
+          is(data[i].path, '/' + data[i].name, "Subdirectory path should be called '/' + leafname");
+          if (aRecursive) {
+            promises.push(checkSubDir(data[i]));
+          }
+        }
+      }
+
+      return Promise.all(promises);
+    },
+    function() {
+      ok(false, "Something when wrong");
+    }
+  ).then(aNext);
+}
+
+function test_getFiles(aDirectory, aRecursive, aNext) {
+  aDirectory.getFiles(aRecursive).then(
+    function(data) {
+      for (var i = 0; i < data.length; ++i) {
+        ok (data[i] instanceof File, "File: " + data[i].name);
+      }
+    },
+    function() {
+      ok(false, "Something when wrong");
+    }
+  ).then(aNext);
+}
+
+function test_getFiles_recursiveComparison(aDirectory, aNext) {
+  aDirectory.getFiles(true).then(function(data) {
+    is(data.length, 2, "Only 2 files for this test.");
+    ok(data[0].name == 'foo.txt' || data[0].name == 'bar.txt', "First filename matches");
+    ok(data[1].name == 'foo.txt' || data[1].name == 'bar.txt', "Second filename matches");
+  }).then(function() {
+    return aDirectory.getFiles(false);
+  }).then(function(data) {
+    is(data.length, 1, "Only 1 file for this test.");
+    ok(data[0].name == 'foo.txt' || data[0].name == 'bar.txt', "First filename matches");
+  }).catch(function() {
+    ok(false, "Something when wrong");
+  }).then(aNext);
+}
--- a/dom/filesystem/tests/mochitest.ini
+++ b/dom/filesystem/tests/mochitest.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 support-files =
+  filesystem_commons.js
   script_fileList.js
   worker_basic.js
 
 [test_basic.html]
 [test_worker_basic.html]
--- a/dom/filesystem/tests/script_fileList.js
+++ b/dom/filesystem/tests/script_fileList.js
@@ -1,25 +1,70 @@
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 Cu.importGlobalProperties(["File"]);
 
-addMessageListener("dir.open", function (e) {
-  var testFile = Cc["@mozilla.org/file/directory_service;1"]
-                   .getService(Ci.nsIDirectoryService)
-                   .QueryInterface(Ci.nsIProperties)
-                   .get(e.path == 'root' ? 'ProfD' : e.path, Ci.nsIFile);
+function createProfDFile() {
+  return Cc["@mozilla.org/file/directory_service;1"]
+           .getService(Ci.nsIDirectoryService)
+           .QueryInterface(Ci.nsIProperties)
+           .get('ProfD', Ci.nsIFile);
+}
+
+function createRootFile() {
+  var testFile = createProfDFile();
 
   // Let's go back to the root of the FileSystem
-  if (e.path == 'root') {
-    while (true) {
-      var parent = testFile.parent;
-      if (!parent) {
-        break;
-      }
+  while (true) {
+    var parent = testFile.parent;
+    if (!parent) {
+      break;
+    }
+
+    testFile = parent;
+  }
+
+  return testFile;
+}
+
+function createTestFile() {
+  var tmpFile = Cc["@mozilla.org/file/directory_service;1"]
+                  .getService(Ci.nsIDirectoryService)
+                  .QueryInterface(Ci.nsIProperties)
+                  .get('TmpD', Ci.nsIFile)
+  tmpFile.append('dir-test');
+  tmpFile.createUnique(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  var file1 = tmpFile.clone();
+  file1.append('foo.txt');
+  file1.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
-      testFile = parent;
-    }
+  var dir = tmpFile.clone();
+  dir.append('subdir');
+  dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  var file2 = dir.clone();
+  file2.append('bar.txt');
+  file2.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+  return tmpFile;
+}
+
+addMessageListener("dir.open", function (e) {
+  var testFile;
+
+  switch (e.path) {
+    case 'ProfD':
+      testFile = createProfDFile();
+      break;
+
+    case 'root':
+      testFile = createRootFile();
+      break;
+
+    case 'test':
+      testFile = createTestFile();
+      break;
   }
 
   sendAsyncMessage("dir.opened", {
     dir: testFile.path
   });
 });
--- a/dom/filesystem/tests/test_basic.html
+++ b/dom/filesystem/tests/test_basic.html
@@ -1,13 +1,14 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for Directory API</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="filesystem_commons.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
 <body>
 <input id="fileList" type="file"></input>
 <script type="application/javascript;version=1.7">
 
 var directory;
@@ -29,73 +30,30 @@ function create_fileList(aPath) {
       next();
     });
   }
 
   script.addMessageListener("dir.opened", onOpened);
   script.sendAsyncMessage("dir.open", { path: aPath });
 }
 
-function test_basic() {
-  ok(directory, "Directory exists.");
-  ok(directory instanceof Directory, "We have a directory.");
-  is(directory.name, '/', "directory.name must be '/'");
-  is(directory.path, '/', "directory.path must be '/'");
-  next();
-}
-
-function checkSubDir(dir) {
-  return dir.getFilesAndDirectories().then(
-    function(data) {
-      for (var i = 0; i < data.length; ++i) {
-        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
-        if (data[i] instanceof Directory) {
-          isnot(data[i].name, '/', "Subdirectory should be called with the leafname: " + data[i].name);
-          isnot(data[i].path, '/', "Subdirectory path should be called with the leafname:" + data[i].path);
-          isnot(data[i].path, dir.path, "Subdirectory path should contain the parent path.");
-          is(data[i].path,dir.path + '/' + data[i].name, "Subdirectory path should be called parentdir.path + '/' + leafname");
-        }
-      }
-    }
-  );
-}
-
-function getFilesAndDirectories(aRecursive) {
-  directory.getFilesAndDirectories().then(
-    function(data) {
-      ok(data.length, "We should have some data.");
-      var promises = [];
-      for (var i = 0; i < data.length; ++i) {
-        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
-        if (data[i] instanceof Directory) {
-          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
-          is(data[i].path, '/' + data[i].name, "Subdirectory path should be called '/' + leafname");
-
-          if (aRecursive) {
-            promises.push(checkSubDir(data[i]));
-          }
-        }
-      }
-
-      return Promise.all(promises);
-    },
-    function() {
-      ok(false, "Something when wrong");
-    }
-  ).then(next);
-}
-
 var tests = [
   function() { create_fileList('ProfD') },
-  test_basic,
-  function() { getFilesAndDirectories(true) },
+  function() { test_basic(directory, next); },
+  function() { test_getFilesAndDirectories(directory, true, next); },
+  function() { test_getFiles(directory, false, next); },
+  function() { test_getFiles(directory, true, next); },
 
-  function() { create_fileList('root') },
-  test_basic,
-  function() { getFilesAndDirectories(false) },
+  function() { create_fileList('test') },
+  function() { test_getFiles_recursiveComparison(directory, next); },
+
+  function() { create_fileList('root'); },
+  function() { test_basic(directory, next); },
+  function() { test_getFilesAndDirectories(directory, false, next); },
+  function() { test_getFiles(directory, false, next); },
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/dom/filesystem/tests/worker_basic.js
+++ b/dom/filesystem/tests/worker_basic.js
@@ -1,56 +1,41 @@
+importScripts('filesystem_commons.js');
+
 function finish() {
   postMessage({ type: 'finish' });
 }
 
 function ok(a, msg) {
   postMessage({ type: 'test', test: !!a, message: msg });
 }
 
 function is(a, b, msg) {
   ok(a === b, msg);
 }
 
 function isnot(a, b, msg) {
   ok(a != b, msg);
 }
 
-function checkSubDir(dir) {
-  return dir.getFilesAndDirectories().then(
-    function(data) {
-      for (var i = 0; i < data.length; ++i) {
-        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
-        if (data[i] instanceof Directory) {
-          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
-          isnot(data[i].path, '/', "Subdirectory path should be called with the leafname");
-          isnot(data[i].path, dir.path, "Subdirectory path should contain the parent path.");
-          is(data[i].path,dir.path + '/' + data[i].name, "Subdirectory path should be called parentdir.path + '/' + leafname");
-        }
-      }
-    }
-  );
+var tests = [
+  function() { test_basic(directory, next); },
+  function() { test_getFilesAndDirectories(directory, true, next); },
+  function() { test_getFiles(directory, false, next); },
+  function() { test_getFiles(directory, true, next); },
+];
+
+function next() {
+  if (!tests.length) {
+    finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
 }
 
+var directory;
+
 onmessage = function(e) {
-  var directory = e.data;
-  ok(directory instanceof Directory, "This is a directory.");
-
-  directory.getFilesAndDirectories().then(
-    function(data) {
-      ok(data.length, "We should have some data.");
-      var promises = [];
-      for (var i = 0; i < data.length; ++i) {
-        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
-        if (data[i] instanceof Directory) {
-          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
-          is(data[i].path, '/' + data[i].name, "Subdirectory path should be called '/' + leafname");
-          promises.push(checkSubDir(data[i]));
-        }
-      }
-
-      return Promise.all(promises);
-    },
-    function() {
-      ok(false, "Something when wrong");
-    }
-  ).then(finish);
+  directory = e.data;
+  next();
 }
--- a/dom/webidl/Directory.webidl
+++ b/dom/webidl/Directory.webidl
@@ -109,16 +109,19 @@ partial interface Directory {
   [Throws]
   readonly attribute DOMString path;
 
   /*
    * Getter for the immediate children of this directory.
    */
   [Throws]
   Promise<sequence<(File or Directory)>> getFilesAndDirectories();
+
+  [Throws]
+  Promise<sequence<File>> getFiles(optional boolean recursiveFlag = false);
 };
 
 enum CreateIfExistsMode { "replace", "fail" };
 
 dictionary CreateFileOptions {
   CreateIfExistsMode ifExists = "fail";
   (DOMString or Blob or ArrayBuffer or ArrayBufferView) data;
 };