Bug 1265767 - Subset of Blink FileSystem API - patch 4 - DirectoryEntry methods, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 07 Jun 2016 00:55:17 +0200
changeset 300832 d22217515a1e6a08dc023d75361853c84d0275c8
parent 300831 7af7af94985a7115d506316e0d19ae841c64446c
child 300833 56fe0bfb503ce68e0bf0c058ecd32126ec8e2438
push id19599
push usercbook@mozilla.com
push dateWed, 08 Jun 2016 10:16:21 +0000
treeherderfx-team@81f4cc3f6f4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1265767
milestone50.0a1
Bug 1265767 - Subset of Blink FileSystem API - patch 4 - DirectoryEntry methods, r=smaug
dom/filesystem/compat/DirectoryEntry.cpp
dom/filesystem/compat/DirectoryEntry.h
dom/filesystem/compat/DirectoryReader.cpp
dom/filesystem/compat/DirectoryReader.h
dom/filesystem/compat/ErrorCallbackRunnable.h
dom/filesystem/compat/tests/test_basic.html
dom/promise/PromiseNativeHandler.h
dom/webidl/DOMFileSystem.webidl
--- a/dom/filesystem/compat/DirectoryEntry.cpp
+++ b/dom/filesystem/compat/DirectoryEntry.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 "DirectoryEntry.h"
+#include "DirectoryReader.h"
 #include "ErrorCallbackRunnable.h"
 #include "mozilla/dom/Directory.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DirectoryEntry, Entry, mDirectory)
 
@@ -44,16 +45,24 @@ DirectoryEntry::GetName(nsAString& aName
 }
 
 void
 DirectoryEntry::GetFullPath(nsAString& aPath, ErrorResult& aRv) const
 {
   mDirectory->GetPath(aPath, aRv);
 }
 
+already_AddRefed<DirectoryReader>
+DirectoryEntry::CreateReader() const
+{
+  RefPtr<DirectoryReader> reader =
+    new DirectoryReader(GetParentObject(), mDirectory);
+  return reader.forget();
+}
+
 void
 DirectoryEntry::RemoveRecursively(VoidCallback& aSuccessCallback,
                                   const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const
 {
   if (aErrorCallback.WasPassed()) {
     RefPtr<ErrorCallbackRunnable> runnable =
       new ErrorCallbackRunnable(GetParentObject(),
                                 &aErrorCallback.Value());
--- a/dom/filesystem/compat/DirectoryEntry.h
+++ b/dom/filesystem/compat/DirectoryEntry.h
@@ -34,21 +34,17 @@ public:
 
   virtual void
   GetName(nsAString& aName, ErrorResult& aRv) const override;
 
   virtual void
   GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const override;
 
   already_AddRefed<DirectoryReader>
-  CreateReader(ErrorResult& aRv) const
-  {
-    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-    return nullptr;
-  }
+  CreateReader() const;
 
   void
   GetFile(const nsAString& aPath, const FileSystemFlags& aFlag,
           const Optional<OwningNonNull<EntryCallback>>& aSuccessCallback,
           const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
           ErrorResult& aRv) const
   {
     aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
--- a/dom/filesystem/compat/DirectoryReader.cpp
+++ b/dom/filesystem/compat/DirectoryReader.cpp
@@ -1,37 +1,201 @@
 /* -*- 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 "DirectoryReader.h"
+#include "FileEntry.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 #include "nsIGlobalObject.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DirectoryReader, mParent)
+namespace {
+
+class EmptyEntriesCallbackRunnable final : public Runnable
+{
+public:
+  EmptyEntriesCallbackRunnable(EntriesCallback* aCallback)
+    : mCallback(aCallback)
+  {
+    MOZ_ASSERT(aCallback);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    Sequence<OwningNonNull<Entry>> sequence;
+    mCallback->HandleEvent(sequence);
+    return NS_OK;
+  }
+
+private:
+  RefPtr<EntriesCallback> mCallback;
+};
+
+class PromiseHandler final : public PromiseNativeHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  PromiseHandler(nsIGlobalObject* aGlobalObject,
+                 EntriesCallback* aSuccessCallback,
+                 ErrorCallback* aErrorCallback)
+    : mGlobal(aGlobalObject)
+    , mSuccessCallback(aSuccessCallback)
+    , mErrorCallback(aErrorCallback)
+  {
+    MOZ_ASSERT(aGlobalObject);
+    MOZ_ASSERT(aSuccessCallback);
+  }
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    if(NS_WARN_IF(!aValue.isObject())) {
+      return;
+    }
+
+    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+    uint32_t length;
+    if (NS_WARN_IF(!JS_GetArrayLength(aCx, obj, &length))) {
+      return;
+    }
+
+    Sequence<OwningNonNull<Entry>> sequence;
+    if (NS_WARN_IF(!sequence.SetLength(length, fallible))) {
+      return;
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+      JS::Rooted<JS::Value> value(aCx);
+      if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &value))) {
+        return;
+      }
+
+      if(NS_WARN_IF(!value.isObject())) {
+        return;
+      }
+
+      JS::Rooted<JSObject*> valueObj(aCx, &value.toObject());
+
+      RefPtr<File> file;
+      if (NS_SUCCEEDED(UNWRAP_OBJECT(File, valueObj, file))) {
+        RefPtr<FileEntry> entry = new FileEntry(mGlobal, file);
+        sequence[i] = entry;
+        continue;
+      }
+
+      RefPtr<Directory> directory;
+      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Directory, valueObj,
+                                             directory)))) {
+        return;
+      }
+
+      RefPtr<DirectoryEntry> entry = new DirectoryEntry(mGlobal, directory);
+      sequence[i] = entry;
+    }
+
+    mSuccessCallback->HandleEvent(sequence);
+  }
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    if (mErrorCallback) {
+      RefPtr<ErrorCallbackRunnable> runnable =
+        new ErrorCallbackRunnable(mGlobal, mErrorCallback,
+                                  NS_ERROR_DOM_INVALID_STATE_ERR);
+      nsresult rv = NS_DispatchToMainThread(runnable);
+      NS_WARN_IF(NS_FAILED(rv));
+    }
+  }
+
+private:
+  ~PromiseHandler() {}
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  RefPtr<EntriesCallback> mSuccessCallback;
+  RefPtr<ErrorCallback> mErrorCallback;
+};
+
+NS_IMPL_ISUPPORTS0(PromiseHandler);
+
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DirectoryReader, mParent, mDirectory)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(DirectoryReader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(DirectoryReader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DirectoryReader)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-DirectoryReader::DirectoryReader(nsIGlobalObject* aGlobal)
+DirectoryReader::DirectoryReader(nsIGlobalObject* aGlobal,
+                                 Directory* aDirectory)
   : mParent(aGlobal)
-{}
+  , mDirectory(aDirectory)
+  , mAlreadyRead(false)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(aDirectory);
+}
 
 DirectoryReader::~DirectoryReader()
 {}
 
 JSObject*
 DirectoryReader::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DirectoryReaderBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+DirectoryReader::ReadEntries(EntriesCallback& aSuccessCallback,
+                             const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+                             ErrorResult& aRv)
+{
+  if (mAlreadyRead) {
+    RefPtr<EmptyEntriesCallbackRunnable> runnable =
+      new EmptyEntriesCallbackRunnable(&aSuccessCallback);
+    aRv = NS_DispatchToMainThread(runnable);
+    NS_WARN_IF(aRv.Failed());
+    return;
+  }
+
+  // This object can be used only once.
+  mAlreadyRead = true;
+
+  ErrorResult rv;
+  RefPtr<Promise> promise = mDirectory->GetFilesAndDirectories(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    if (aErrorCallback.WasPassed()) {
+      RefPtr<ErrorCallbackRunnable> runnable =
+        new ErrorCallbackRunnable(GetParentObject(),
+                                  &aErrorCallback.Value(),
+                                  rv.StealNSResult());
+      aRv = NS_DispatchToMainThread(runnable);
+      NS_WARN_IF(aRv.Failed());
+    }
+
+    return;
+  }
+
+  RefPtr<PromiseHandler> handler =
+    new PromiseHandler(GetParentObject(), &aSuccessCallback,
+                       aErrorCallback.WasPassed()
+                         ? &aErrorCallback.Value() : nullptr);
+  promise->AppendNativeHandler(handler);
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/filesystem/compat/DirectoryReader.h
+++ b/dom/filesystem/compat/DirectoryReader.h
@@ -13,45 +13,48 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
+class Directory;
+
 class DirectoryReader final
   : public nsISupports
   , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DirectoryReader)
 
-  explicit DirectoryReader(nsIGlobalObject* aGlobalObject);
+  explicit DirectoryReader(nsIGlobalObject* aGlobalObject,
+                           Directory* aDirectory);
 
   nsIGlobalObject*
   GetParentObject() const
   {
     return mParent;
   }
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
   ReadEntries(EntriesCallback& aSuccessCallback,
               const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
-              ErrorResult& aRv) const
-  {
-    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  }
+              ErrorResult& aRv);
 
 private:
   ~DirectoryReader();
 
   nsCOMPtr<nsIGlobalObject> mParent;
+  RefPtr<Directory> mDirectory;
+
+  bool mAlreadyRead;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_DirectoryReader_h
--- a/dom/filesystem/compat/ErrorCallbackRunnable.h
+++ b/dom/filesystem/compat/ErrorCallbackRunnable.h
@@ -12,39 +12,42 @@
 
 namespace mozilla {
 namespace dom {
 
 class ErrorCallbackRunnable final : public Runnable
 {
 public:
   explicit ErrorCallbackRunnable(nsIGlobalObject* aGlobalObject,
-                                 ErrorCallback* aCallback)
+                                 ErrorCallback* aCallback,
+                                 nsresult aError = NS_ERROR_DOM_NOT_SUPPORTED_ERR)
     : mGlobal(aGlobalObject)
     , mCallback(aCallback)
+    , mError(aError)
   {
     MOZ_ASSERT(aGlobalObject);
     MOZ_ASSERT(aCallback);
+    MOZ_ASSERT(NS_FAILED(aError));
   }
 
   NS_IMETHOD
   Run() override
   {
     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
     if (NS_WARN_IF(!window)) {
       return NS_ERROR_FAILURE;
     }
 
-    RefPtr<DOMError> error =
-      new DOMError(window, NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    RefPtr<DOMError> error = new DOMError(window, mError);
     mCallback->HandleEvent(*error);
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIGlobalObject> mGlobal;
   RefPtr<ErrorCallback> mCallback;
+  nsresult mError;
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_ErrorCallbackRunnable_h
--- a/dom/filesystem/compat/tests/test_basic.html
+++ b/dom/filesystem/compat/tests/test_basic.html
@@ -84,27 +84,55 @@ function test_fileEntry_createWriter() {
 function test_directoryEntry() {
   ok("name" in directoryEntry, "We have a name.");
   ok("fullPath" in directoryEntry, "We have a fullPath.");
   ok("filesystem" in directoryEntry, "We have a filesystem.");
 
   next();
 }
 
+function test_directoryEntry_createReader() {
+  var reader = directoryEntry.createReader();
+  ok(reader, "We have a DirectoryReader");
+
+  reader.readEntries(function(a) {
+    ok(Array.isArray(a), "We want an array.");
+    is(a.length, 2, "reader.readyEntries returns 2 elements.");
+
+    for (var i = 0; i < 2; ++i) {
+      ok(a[i].name == "subdir" || a[i].name == "foo.txt", "Correct names");
+      is(a[i].fullPath, directoryEntry.fullPath + "/" + a[i].name, "FullPath is correct");
+    }
+
+    // Called twice:
+    reader.readEntries(function(a) {
+      ok(Array.isArray(a), "We want an array.");
+      is(a.length, 0, "reader.readyEntries returns 0 elements.");
+      next();
+    }, function() {
+      ok(false, "Something when wrong!");
+    });
+
+  }, function() {
+    ok(false, "Something when wrong!");
+  });
+}
+
 var tests = [
   setup_tests,
   populate_entries,
 
   test_entries,
 
   test_fileEntry,
   test_fileEntry_file,
   test_fileEntry_createWriter,
 
   test_directoryEntry,
+  test_directoryEntry_createReader,
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/dom/promise/PromiseNativeHandler.h
+++ b/dom/promise/PromiseNativeHandler.h
@@ -27,17 +27,17 @@ protected:
 public:
   virtual void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) = 0;
 
   virtual void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) = 0;
 
 #ifdef SPIDERMONKEY_PROMISE
-    bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+  bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
                   JS::MutableHandle<JSObject*> aWrapper);
 #endif // SPIDERMONKEY_PROMISE
 
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/webidl/DOMFileSystem.webidl
+++ b/dom/webidl/DOMFileSystem.webidl
@@ -38,17 +38,16 @@ callback interface EntryCallback {
 };
 
 callback interface VoidCallback {
     void handleEvent();
 };
 
 [NoInterfaceObject]
 interface DirectoryEntry : Entry {
-    [Throws]
     DirectoryReader createReader();
 
     [Throws]
     void getFile(DOMString? path, optional FileSystemFlags options, optional EntryCallback successCallback, optional ErrorCallback errorCallback);
 
     [Throws]
     void getDirectory(DOMString? path, optional FileSystemFlags options, optional EntryCallback successCallback, optional ErrorCallback errorCallback);
 
@@ -63,16 +62,19 @@ callback interface EntriesCallback {
 
 callback interface ErrorCallback {
     // This should be FileError but we are implementing just a subset of this API.
     void handleEvent(DOMError error);
 };
 
 [NoInterfaceObject]
 interface DirectoryReader {
+
+    // readEntries can be called just once. The second time it returns no data.
+
     [Throws]
     void readEntries (EntriesCallback successCallback, optional ErrorCallback errorCallback);
 };
 
 callback interface BlobCallback {
     void handleEvent(Blob? blob);
 };