summaryrefslogtreecommitdiffstats
path: root/dom/filesystem
diff options
context:
space:
mode:
Diffstat (limited to 'dom/filesystem')
-rw-r--r--dom/filesystem/Directory.cpp257
-rw-r--r--dom/filesystem/Directory.h131
-rw-r--r--dom/filesystem/FileSystemBase.cpp169
-rw-r--r--dom/filesystem/FileSystemBase.h118
-rw-r--r--dom/filesystem/FileSystemRequestParent.cpp194
-rw-r--r--dom/filesystem/FileSystemRequestParent.h52
-rw-r--r--dom/filesystem/FileSystemSecurity.cpp119
-rw-r--r--dom/filesystem/FileSystemSecurity.h48
-rw-r--r--dom/filesystem/FileSystemTaskBase.cpp379
-rw-r--r--dom/filesystem/FileSystemTaskBase.h279
-rw-r--r--dom/filesystem/FileSystemUtils.cpp75
-rw-r--r--dom/filesystem/FileSystemUtils.h44
-rw-r--r--dom/filesystem/GetDirectoryListingTask.cpp390
-rw-r--r--dom/filesystem/GetDirectoryListingTask.h106
-rw-r--r--dom/filesystem/GetFileOrDirectoryTask.cpp280
-rw-r--r--dom/filesystem/GetFileOrDirectoryTask.h89
-rw-r--r--dom/filesystem/GetFilesHelper.cpp643
-rw-r--r--dom/filesystem/GetFilesHelper.h205
-rw-r--r--dom/filesystem/GetFilesTask.cpp263
-rw-r--r--dom/filesystem/GetFilesTask.h95
-rw-r--r--dom/filesystem/OSFileSystem.cpp113
-rw-r--r--dom/filesystem/OSFileSystem.h132
-rw-r--r--dom/filesystem/PFileSystemParams.ipdlh50
-rw-r--r--dom/filesystem/PFileSystemRequest.ipdl73
-rw-r--r--dom/filesystem/compat/CallbackRunnables.cpp306
-rw-r--r--dom/filesystem/compat/CallbackRunnables.h128
-rw-r--r--dom/filesystem/compat/FileSystem.cpp75
-rw-r--r--dom/filesystem/compat/FileSystem.h73
-rw-r--r--dom/filesystem/compat/FileSystemDirectoryEntry.cpp107
-rw-r--r--dom/filesystem/compat/FileSystemDirectoryEntry.h84
-rw-r--r--dom/filesystem/compat/FileSystemDirectoryReader.cpp188
-rw-r--r--dom/filesystem/compat/FileSystemDirectoryReader.h64
-rw-r--r--dom/filesystem/compat/FileSystemEntry.cpp89
-rw-r--r--dom/filesystem/compat/FileSystemEntry.h89
-rw-r--r--dom/filesystem/compat/FileSystemFileEntry.cpp138
-rw-r--r--dom/filesystem/compat/FileSystemFileEntry.h57
-rw-r--r--dom/filesystem/compat/FileSystemRootDirectoryEntry.cpp146
-rw-r--r--dom/filesystem/compat/FileSystemRootDirectoryEntry.h53
-rw-r--r--dom/filesystem/compat/FileSystemRootDirectoryReader.cpp96
-rw-r--r--dom/filesystem/compat/FileSystemRootDirectoryReader.h41
-rw-r--r--dom/filesystem/compat/moz.build30
-rw-r--r--dom/filesystem/compat/tests/mochitest.ini8
-rw-r--r--dom/filesystem/compat/tests/moz.build7
-rw-r--r--dom/filesystem/compat/tests/script_entries.js47
-rw-r--r--dom/filesystem/compat/tests/test_basic.html494
-rw-r--r--dom/filesystem/compat/tests/test_formSubmission.html267
-rw-r--r--dom/filesystem/compat/tests/test_no_dnd.html85
-rw-r--r--dom/filesystem/moz.build48
-rw-r--r--dom/filesystem/tests/filesystem_commons.js103
-rw-r--r--dom/filesystem/tests/mochitest.ini10
-rw-r--r--dom/filesystem/tests/moz.build7
-rw-r--r--dom/filesystem/tests/script_fileList.js129
-rw-r--r--dom/filesystem/tests/test_basic.html167
-rw-r--r--dom/filesystem/tests/test_bug1319088.html66
-rw-r--r--dom/filesystem/tests/test_webkitdirectory.html191
-rw-r--r--dom/filesystem/tests/test_worker_basic.html72
-rw-r--r--dom/filesystem/tests/worker_basic.js41
57 files changed, 7810 insertions, 0 deletions
diff --git a/dom/filesystem/Directory.cpp b/dom/filesystem/Directory.cpp
new file mode 100644
index 000000000..59c78fb2c
--- /dev/null
+++ b/dom/filesystem/Directory.cpp
@@ -0,0 +1,257 @@
+/* -*- 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 "mozilla/dom/Directory.h"
+
+#include "GetDirectoryListingTask.h"
+#include "GetFilesTask.h"
+#include "WorkerPrivate.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"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Directory)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Directory)
+ if (tmp->mFileSystem) {
+ tmp->mFileSystem->Unlink();
+ tmp->mFileSystem = nullptr;
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Directory)
+ if (tmp->mFileSystem) {
+ tmp->mFileSystem->Traverse(cb);
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Directory)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Directory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Directory)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Directory)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* static */ bool
+Directory::WebkitBlinkDirectoryPickerEnabled(JSContext* aCx, JSObject* aObj)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false);
+ }
+
+ // aCx can be null when this function is called by something else than WebIDL
+ // binding code.
+ workers::WorkerPrivate* workerPrivate =
+ workers::GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate) {
+ return false;
+ }
+
+ return workerPrivate->WebkitBlinkDirectoryPickerEnabled();
+}
+
+/* static */ already_AddRefed<Directory>
+Directory::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aRealPath,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIFile> path;
+ aRv = NS_NewLocalFile(aRealPath, true, getter_AddRefs(path));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return Create(aGlobal.GetAsSupports(), path);
+}
+
+/* static */ already_AddRefed<Directory>
+Directory::Create(nsISupports* aParent, nsIFile* aFile,
+ FileSystemBase* aFileSystem)
+{
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(aFile);
+
+#ifdef DEBUG
+ bool isDir;
+ nsresult rv = aFile->IsDirectory(&isDir);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && isDir);
+#endif
+
+ RefPtr<Directory> directory = new Directory(aParent, aFile, aFileSystem);
+ return directory.forget();
+}
+
+Directory::Directory(nsISupports* aParent,
+ nsIFile* aFile,
+ FileSystemBase* aFileSystem)
+ : mParent(aParent)
+ , mFile(aFile)
+{
+ MOZ_ASSERT(aFile);
+
+ // aFileSystem can be null. In this case we create a OSFileSystem when needed.
+ if (aFileSystem) {
+ // More likely, this is a OSFileSystem. This object keeps a reference of
+ // mParent but it's not cycle collectable and to avoid manual
+ // addref/release, it's better to have 1 object per directory. For this
+ // reason we clone it here.
+ mFileSystem = aFileSystem->Clone();
+ }
+}
+
+Directory::~Directory()
+{
+}
+
+nsISupports*
+Directory::GetParentObject() const
+{
+ return mParent;
+}
+
+JSObject*
+Directory::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DirectoryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+Directory::GetName(nsAString& aRetval, ErrorResult& aRv)
+{
+ aRetval.Truncate();
+
+ RefPtr<FileSystemBase> fs = GetFileSystem(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ fs->GetDirectoryName(mFile, aRetval, aRv);
+}
+
+void
+Directory::GetPath(nsAString& aRetval, ErrorResult& aRv)
+{
+ // This operation is expensive. Better to cache the result.
+ if (mPath.IsEmpty()) {
+ RefPtr<FileSystemBase> fs = GetFileSystem(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ fs->GetDOMPath(mFile, mPath, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ aRetval = mPath;
+}
+
+nsresult
+Directory::GetFullRealPath(nsAString& aPath)
+{
+ nsresult rv = mFile->GetPath(aPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<Promise>
+Directory::GetFilesAndDirectories(ErrorResult& aRv)
+{
+ RefPtr<FileSystemBase> fs = GetFileSystem(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<GetDirectoryListingTaskChild> task =
+ GetDirectoryListingTaskChild::Create(fs, this, mFile, mFilters, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ task->Start();
+
+ 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, this, mFile, aRecursiveFlag, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ task->Start();
+
+ return task->GetPromise();
+}
+
+void
+Directory::SetContentFilters(const nsAString& aFilters)
+{
+ mFilters = aFilters;
+}
+
+FileSystemBase*
+Directory::GetFileSystem(ErrorResult& aRv)
+{
+ if (!mFileSystem) {
+ nsAutoString path;
+ aRv = mFile->GetPath(path);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<OSFileSystem> fs = new OSFileSystem(path);
+ fs->Init(mParent);
+
+ mFileSystem = fs;
+ }
+
+ return mFileSystem;
+}
+
+
+bool
+Directory::ClonableToDifferentThreadOrProcess() const
+{
+ // If we don't have a fileSystem we are going to create a OSFileSystem that is
+ // clonable everywhere.
+ if (!mFileSystem) {
+ return true;
+ }
+
+ return mFileSystem->ClonableToDifferentThreadOrProcess();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/Directory.h b/dom/filesystem/Directory.h
new file mode 100644
index 000000000..3a532e043
--- /dev/null
+++ b/dom/filesystem/Directory.h
@@ -0,0 +1,131 @@
+/* -*- 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_Directory_h
+#define mozilla_dom_Directory_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/File.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemBase;
+class Promise;
+class StringOrFileOrDirectory;
+
+class Directory final
+ : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Directory)
+
+ static bool
+ WebkitBlinkDirectoryPickerEnabled(JSContext* aCx, JSObject* aObj);
+
+ static already_AddRefed<Directory>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aRealPath,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Directory>
+ Create(nsISupports* aParent, nsIFile* aDirectory,
+ FileSystemBase* aFileSystem = 0);
+
+ // ========= Begin WebIDL bindings. ===========
+
+ nsISupports*
+ GetParentObject() const;
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void
+ GetName(nsAString& aRetval, ErrorResult& aRv);
+
+ // From https://microsoftedge.github.io/directory-upload/proposal.html#directory-interface :
+
+ void
+ 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:
+ *
+ * * filter-out-sensitive
+ * This keyword filters out files or directories that we don't wish to
+ * make available to Web content because we are concerned that there is
+ * a risk that users may unwittingly give Web content access to them
+ * and suffer undesirable consequences. The details of what is
+ * filtered out can be found in GetDirectoryListingTask::Work.
+ *
+ * In future, we will likely support filtering based on filename extensions
+ * (for example, aFilters could be "*.jpg; *.jpeg; *.gif"), but that isn't
+ * supported yet. Once supported, files that don't match a specified
+ * extension (if any are specified) would be filtered out. This
+ * functionality would allow us to apply the 'accept' attribute from
+ * <input type=file directory accept="..."> to the results of a directory
+ * picker operation.
+ */
+ void
+ SetContentFilters(const nsAString& aFilters);
+
+ FileSystemBase*
+ GetFileSystem(ErrorResult& aRv);
+
+ bool
+ ClonableToDifferentThreadOrProcess() const;
+
+ nsIFile*
+ GetInternalNsIFile() const
+ {
+ return mFile;
+ }
+
+private:
+ Directory(nsISupports* aParent,
+ nsIFile* aFile,
+ FileSystemBase* aFileSystem = nullptr);
+ ~Directory();
+
+ /*
+ * Convert relative DOM path to the absolute real path.
+ */
+ nsresult
+ DOMPathToRealPath(const nsAString& aPath, nsIFile** aFile) const;
+
+ nsCOMPtr<nsISupports> mParent;
+ RefPtr<FileSystemBase> mFileSystem;
+ nsCOMPtr<nsIFile> mFile;
+
+ nsString mFilters;
+ nsString mPath;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Directory_h
diff --git a/dom/filesystem/FileSystemBase.cpp b/dom/filesystem/FileSystemBase.cpp
new file mode 100644
index 000000000..f44f4fbf1
--- /dev/null
+++ b/dom/filesystem/FileSystemBase.cpp
@@ -0,0 +1,169 @@
+/* -*- 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 "mozilla/dom/FileSystemBase.h"
+
+#include "nsCharSeparatedTokenizer.h"
+#include "OSFileSystem.h"
+
+namespace mozilla {
+namespace dom {
+
+FileSystemBase::FileSystemBase()
+ : mShutdown(false)
+#ifdef DEBUG
+ , mOwningThread(PR_GetCurrentThread())
+#endif
+{
+}
+
+FileSystemBase::~FileSystemBase()
+{
+ AssertIsOnOwningThread();
+}
+
+void
+FileSystemBase::Shutdown()
+{
+ AssertIsOnOwningThread();
+ mShutdown = true;
+}
+
+nsISupports*
+FileSystemBase::GetParentObject() const
+{
+ AssertIsOnOwningThread();
+ return nullptr;
+}
+
+bool
+FileSystemBase::GetRealPath(BlobImpl* aFile, nsIFile** aPath) const
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFile, "aFile Should not be null.");
+ MOZ_ASSERT(aPath);
+
+ nsAutoString filePath;
+ ErrorResult rv;
+ aFile->GetMozFullPathInternal(filePath, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ rv = NS_NewLocalFile(filePath, true, aPath);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ return true;
+}
+
+bool
+FileSystemBase::IsSafeFile(nsIFile* aFile) const
+{
+ AssertIsOnOwningThread();
+ return false;
+}
+
+bool
+FileSystemBase::IsSafeDirectory(Directory* aDir) const
+{
+ AssertIsOnOwningThread();
+ return false;
+}
+
+void
+FileSystemBase::GetDirectoryName(nsIFile* aFile, nsAString& aRetval,
+ ErrorResult& aRv) const
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFile);
+
+ aRv = aFile->GetLeafName(aRetval);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "GetLeafName failed");
+}
+
+void
+FileSystemBase::GetDOMPath(nsIFile* aFile,
+ nsAString& aRetval,
+ ErrorResult& aRv) const
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFile);
+
+ aRetval.Truncate();
+
+ nsCOMPtr<nsIFile> fileSystemPath;
+ aRv = NS_NewLocalFile(LocalRootPath(), true, getter_AddRefs(fileSystemPath));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsIFile> path;
+ aRv = aFile->Clone(getter_AddRefs(path));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsTArray<nsString> parts;
+
+ while (true) {
+ nsAutoString leafName;
+ aRv = path->GetLeafName(leafName);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!leafName.IsEmpty()) {
+ parts.AppendElement(leafName);
+ }
+
+ bool equal = false;
+ aRv = fileSystemPath->Equals(path, &equal);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (equal) {
+ break;
+ }
+
+ nsCOMPtr<nsIFile> parentPath;
+ aRv = path->GetParent(getter_AddRefs(parentPath));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(parentPath);
+
+ aRv = parentPath->Clone(getter_AddRefs(path));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ if (parts.IsEmpty()) {
+ aRetval.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ return;
+ }
+
+ for (int32_t i = parts.Length() - 1; i >= 0; --i) {
+ aRetval.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ aRetval.Append(parts[i]);
+ }
+}
+
+void
+FileSystemBase::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mOwningThread);
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/FileSystemBase.h b/dom/filesystem/FileSystemBase.h
new file mode 100644
index 000000000..0b875eb14
--- /dev/null
+++ b/dom/filesystem/FileSystemBase.h
@@ -0,0 +1,118 @@
+/* -*- 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_FileSystemBase_h
+#define mozilla_dom_FileSystemBase_h
+
+#include "nsString.h"
+#include "Directory.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+
+class FileSystemBase
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(FileSystemBase)
+
+ FileSystemBase();
+
+ virtual void
+ Shutdown();
+
+ // SerializeDOMPath the FileSystem to string.
+ virtual void
+ SerializeDOMPath(nsAString& aOutput) const = 0;
+
+ virtual already_AddRefed<FileSystemBase>
+ Clone() = 0;
+
+ virtual bool
+ ShouldCreateDirectory() = 0;
+
+ virtual nsISupports*
+ GetParentObject() const;
+
+ virtual void
+ GetDirectoryName(nsIFile* aFile, nsAString& aRetval,
+ ErrorResult& aRv) const;
+
+ void
+ GetDOMPath(nsIFile* aFile, nsAString& aRetval, ErrorResult& aRv) const;
+
+ /*
+ * Return the local root path of the FileSystem implementation.
+ * For OSFileSystem, this is equal to the path of the root Directory;
+ * For DeviceStorageFileSystem, this is the path of the SDCard, parent
+ * directory of the exposed root Directory (per type).
+ */
+ const nsAString&
+ LocalRootPath() const
+ {
+ return mLocalRootPath;
+ }
+
+ bool
+ IsShutdown() const
+ {
+ return mShutdown;
+ }
+
+ virtual bool
+ IsSafeFile(nsIFile* aFile) const;
+
+ virtual bool
+ IsSafeDirectory(Directory* aDir) const;
+
+ bool
+ GetRealPath(BlobImpl* aFile, nsIFile** aPath) const;
+
+ // IPC initialization
+ // See how these 2 methods are used in FileSystemTaskChildBase.
+
+ virtual bool
+ NeedToGoToMainThread() const { return false; }
+
+ virtual nsresult
+ MainThreadWork() { return NS_ERROR_FAILURE; }
+
+ virtual bool
+ ClonableToDifferentThreadOrProcess() const { return false; }
+
+ // CC methods
+ virtual void Unlink() {}
+ virtual void Traverse(nsCycleCollectionTraversalCallback &cb) {}
+
+ void
+ AssertIsOnOwningThread() const;
+
+protected:
+ virtual ~FileSystemBase();
+
+ // The local path of the root (i.e. the OS path, with OS path separators, of
+ // the OS directory that acts as the root of this OSFileSystem).
+ // This path must be set by the FileSystem implementation immediately
+ // because it will be used for the validation of any FileSystemTaskChildBase.
+ // The concept of this path is that, any task will never go out of it and this
+ // must be considered the OS 'root' of the current FileSystem. Different
+ // Directory object can have different OS 'root' path.
+ // To be more clear, any path managed by this FileSystem implementation must
+ // be discendant of this local root path.
+ nsString mLocalRootPath;
+
+ bool mShutdown;
+
+#ifdef DEBUG
+ PRThread* mOwningThread;
+#endif
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemBase_h
diff --git a/dom/filesystem/FileSystemRequestParent.cpp b/dom/filesystem/FileSystemRequestParent.cpp
new file mode 100644
index 000000000..0b9d6c18a
--- /dev/null
+++ b/dom/filesystem/FileSystemRequestParent.cpp
@@ -0,0 +1,194 @@
+/* -*- 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 "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/PFileSystemParams.h"
+
+#include "GetDirectoryListingTask.h"
+#include "GetFileOrDirectoryTask.h"
+
+#include "mozilla/AppProcessChecker.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemSecurity.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+FileSystemRequestParent::FileSystemRequestParent()
+ : mDestroyed(false)
+{
+ AssertIsOnBackgroundThread();
+}
+
+FileSystemRequestParent::~FileSystemRequestParent()
+{
+ AssertIsOnBackgroundThread();
+}
+
+#define FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(name) \
+ case FileSystemParams::TFileSystem##name##Params: { \
+ const FileSystem##name##Params& p = aParams; \
+ mFileSystem = new OSFileSystemParent(p.filesystem()); \
+ MOZ_ASSERT(mFileSystem); \
+ mTask = name##TaskParent::Create(mFileSystem, p, this, rv); \
+ if (NS_WARN_IF(rv.Failed())) { \
+ rv.SuppressException(); \
+ return false; \
+ } \
+ break; \
+ }
+
+bool
+FileSystemRequestParent::Initialize(const FileSystemParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+
+ ErrorResult rv;
+
+ switch (aParams.type()) {
+
+ FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetDirectoryListing)
+ FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFileOrDirectory)
+ FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFiles)
+
+ default: {
+ NS_RUNTIMEABORT("not reached");
+ break;
+ }
+ }
+
+ if (NS_WARN_IF(!mTask || !mFileSystem)) {
+ // Should never reach here.
+ return false;
+ }
+
+ return true;
+}
+
+namespace {
+
+class CheckPermissionRunnable final : public Runnable
+{
+public:
+ CheckPermissionRunnable(already_AddRefed<ContentParent> aParent,
+ FileSystemRequestParent* aActor,
+ FileSystemTaskParentBase* aTask,
+ const nsAString& aPath)
+ : mContentParent(aParent)
+ , mActor(aActor)
+ , mTask(aTask)
+ , mPath(aPath)
+ , mBackgroundEventTarget(NS_GetCurrentThread())
+ {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(mContentParent);
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mTask);
+ MOZ_ASSERT(mBackgroundEventTarget);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ if (NS_IsMainThread()) {
+ auto raii = mozilla::MakeScopeExit([&] { mContentParent = nullptr; });
+
+
+ if (!mozilla::Preferences::GetBool("dom.filesystem.pathcheck.disabled", false)) {
+ RefPtr<FileSystemSecurity> fss = FileSystemSecurity::Get();
+ if (NS_WARN_IF(!fss ||
+ !fss->ContentProcessHasAccessTo(mContentParent->ChildID(),
+ mPath))) {
+ mContentParent->KillHard("This path is not allowed.");
+ return NS_OK;
+ }
+ }
+
+ return mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ }
+
+ AssertIsOnBackgroundThread();
+
+ // It can happen that this actor has been destroyed in the meantime we were
+ // on the main-thread.
+ if (!mActor->Destroyed()) {
+ mTask->Start();
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~CheckPermissionRunnable()
+ {
+ NS_ProxyRelease(mBackgroundEventTarget, mActor.forget());
+ }
+
+ RefPtr<ContentParent> mContentParent;
+ RefPtr<FileSystemRequestParent> mActor;
+ RefPtr<FileSystemTaskParentBase> mTask;
+ const nsString mPath;
+
+ nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
+};
+
+} // anonymous
+
+void
+FileSystemRequestParent::Start()
+{
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(mFileSystem);
+ MOZ_ASSERT(mTask);
+
+ nsAutoString path;
+ if (NS_WARN_IF(NS_FAILED(mTask->GetTargetPath(path)))) {
+ Unused << Send__delete__(this, FileSystemErrorResponse(NS_ERROR_DOM_SECURITY_ERR));
+ return;
+ }
+
+ RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(Manager());
+
+ // If the ContentParent is null we are dealing with a same-process actor.
+ if (!parent) {
+ mTask->Start();
+ return;
+ }
+
+ RefPtr<Runnable> runnable =
+ new CheckPermissionRunnable(parent.forget(), this, mTask, path);
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+FileSystemRequestParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mDestroyed);
+
+ if (!mFileSystem) {
+ return;
+ }
+
+ mFileSystem->Shutdown();
+ mFileSystem = nullptr;
+ mTask = nullptr;
+ mDestroyed = true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/FileSystemRequestParent.h b/dom/filesystem/FileSystemRequestParent.h
new file mode 100644
index 000000000..a5f4cd6cc
--- /dev/null
+++ b/dom/filesystem/FileSystemRequestParent.h
@@ -0,0 +1,52 @@
+/* -*- 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_FileSystemRequestParent_h
+#define mozilla_dom_FileSystemRequestParent_h
+
+#include "mozilla/dom/PFileSystemRequestParent.h"
+#include "mozilla/dom/FileSystemBase.h"
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemParams;
+class FileSystemTaskParentBase;
+
+class FileSystemRequestParent final : public PFileSystemRequestParent
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemRequestParent)
+
+public:
+ FileSystemRequestParent();
+
+ bool
+ Initialize(const FileSystemParams& aParams);
+
+ void
+ Start();
+
+ bool Destroyed() const
+ {
+ return mDestroyed;
+ }
+
+ virtual void
+ ActorDestroy(ActorDestroyReason why) override;
+
+private:
+ ~FileSystemRequestParent();
+
+ RefPtr<FileSystemBase> mFileSystem;
+ RefPtr<FileSystemTaskParentBase> mTask;
+
+ bool mDestroyed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemRequestParent_h
diff --git a/dom/filesystem/FileSystemSecurity.cpp b/dom/filesystem/FileSystemSecurity.cpp
new file mode 100644
index 000000000..b3d425817
--- /dev/null
+++ b/dom/filesystem/FileSystemSecurity.cpp
@@ -0,0 +1,119 @@
+/* -*- 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 "FileSystemSecurity.h"
+#include "FileSystemUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+StaticRefPtr<FileSystemSecurity> gFileSystemSecurity;
+
+} // anonymous
+
+/* static */ already_AddRefed<FileSystemSecurity>
+FileSystemSecurity::Get()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AssertIsInMainProcess();
+
+ RefPtr<FileSystemSecurity> service = gFileSystemSecurity.get();
+ return service.forget();
+}
+
+/* static */ already_AddRefed<FileSystemSecurity>
+FileSystemSecurity::GetOrCreate()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AssertIsInMainProcess();
+
+ if (!gFileSystemSecurity) {
+ gFileSystemSecurity = new FileSystemSecurity();
+ ClearOnShutdown(&gFileSystemSecurity);
+ }
+
+ RefPtr<FileSystemSecurity> service = gFileSystemSecurity.get();
+ return service.forget();
+}
+
+FileSystemSecurity::FileSystemSecurity()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AssertIsInMainProcess();
+}
+
+FileSystemSecurity::~FileSystemSecurity()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AssertIsInMainProcess();
+}
+
+void
+FileSystemSecurity::GrantAccessToContentProcess(ContentParentId aId,
+ const nsAString& aDirectoryPath)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AssertIsInMainProcess();
+
+ nsTArray<nsString>* paths;
+ if (!mPaths.Get(aId, &paths)) {
+ paths = new nsTArray<nsString>();
+ mPaths.Put(aId, paths);
+ } else if (paths->Contains(aDirectoryPath)) {
+ return;
+ }
+
+ paths->AppendElement(aDirectoryPath);
+}
+
+void
+FileSystemSecurity::Forget(ContentParentId aId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AssertIsInMainProcess();
+
+ mPaths.Remove(aId);
+}
+
+bool
+FileSystemSecurity::ContentProcessHasAccessTo(ContentParentId aId,
+ const nsAString& aPath)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AssertIsInMainProcess();
+
+#if defined(XP_WIN)
+ if (StringBeginsWith(aPath, NS_LITERAL_STRING("..\\")) ||
+ FindInReadable(NS_LITERAL_STRING("\\..\\"), aPath)) {
+ return false;
+ }
+#elif defined(XP_UNIX)
+ if (StringBeginsWith(aPath, NS_LITERAL_STRING("../")) ||
+ FindInReadable(NS_LITERAL_STRING("/../"), aPath)) {
+ return false;
+ }
+#endif
+
+ nsTArray<nsString>* paths;
+ if (!mPaths.Get(aId, &paths)) {
+ return false;
+ }
+
+ for (uint32_t i = 0, len = paths->Length(); i < len; ++i) {
+ if (FileSystemUtils::IsDescendantPath(paths->ElementAt(i), aPath)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/FileSystemSecurity.h b/dom/filesystem/FileSystemSecurity.h
new file mode 100644
index 000000000..fe61dc7c6
--- /dev/null
+++ b/dom/filesystem/FileSystemSecurity.h
@@ -0,0 +1,48 @@
+/* -*- 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_FileSystemSecurity_h
+#define mozilla_dom_FileSystemSecurity_h
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsClassHashtable.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemSecurity final
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(FileSystemSecurity)
+
+ static already_AddRefed<FileSystemSecurity>
+ Get();
+
+ static already_AddRefed<FileSystemSecurity>
+ GetOrCreate();
+
+ void
+ GrantAccessToContentProcess(ContentParentId aId,
+ const nsAString& aDirectoryPath);
+
+ void
+ Forget(ContentParentId aId);
+
+ bool
+ ContentProcessHasAccessTo(ContentParentId aId, const nsAString& aPath);
+
+private:
+ FileSystemSecurity();
+ ~FileSystemSecurity();
+
+ nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mPaths;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_FileSystemSecurity_h
diff --git a/dom/filesystem/FileSystemTaskBase.cpp b/dom/filesystem/FileSystemTaskBase.cpp
new file mode 100644
index 000000000..481002b7d
--- /dev/null
+++ b/dom/filesystem/FileSystemTaskBase.cpp
@@ -0,0 +1,379 @@
+/* -*- 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 "mozilla/dom/FileSystemTaskBase.h"
+
+#include "nsNetCID.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+nsresult
+FileSystemErrorFromNsError(const nsresult& aErrorValue)
+{
+ uint16_t module = NS_ERROR_GET_MODULE(aErrorValue);
+ if (module == NS_ERROR_MODULE_DOM_FILESYSTEM ||
+ module == NS_ERROR_MODULE_DOM_FILE ||
+ module == NS_ERROR_MODULE_DOM) {
+ return aErrorValue;
+ }
+
+ switch (aErrorValue) {
+ case NS_OK:
+ return NS_OK;
+
+ case NS_ERROR_FILE_INVALID_PATH:
+ case NS_ERROR_FILE_UNRECOGNIZED_PATH:
+ return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+
+ case NS_ERROR_FILE_DESTINATION_NOT_DIR:
+ return NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR;
+
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ case NS_ERROR_FILE_DIR_NOT_EMPTY:
+ return NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR;
+
+ case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
+ case NS_ERROR_NOT_AVAILABLE:
+ return NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
+
+ case NS_ERROR_FILE_ALREADY_EXISTS:
+ return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR;
+
+ case NS_ERROR_FILE_NOT_DIRECTORY:
+ return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+
+ case NS_ERROR_UNEXPECTED:
+ default:
+ return NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR;
+ }
+}
+
+nsresult
+DispatchToIOThread(nsIRunnable* aRunnable)
+{
+ MOZ_ASSERT(aRunnable);
+
+ nsCOMPtr<nsIEventTarget> target
+ = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+
+ return target->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+}
+
+// This runnable is used when an error value is set before doing any real
+// operation on the I/O thread. In this case we skip all and we directly
+// communicate the error.
+class ErrorRunnable final : public CancelableRunnable
+{
+public:
+ explicit ErrorRunnable(FileSystemTaskChildBase* aTask)
+ : mTask(aTask)
+ {
+ MOZ_ASSERT(aTask);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mTask->HasError());
+
+ mTask->HandlerCallback();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<FileSystemTaskChildBase> mTask;
+};
+
+} // anonymous namespace
+
+NS_IMPL_ISUPPORTS(FileSystemTaskChildBase, nsIIPCBackgroundChildCreateCallback)
+
+/**
+ * FileSystemTaskBase class
+ */
+
+FileSystemTaskChildBase::FileSystemTaskChildBase(FileSystemBase* aFileSystem)
+ : mErrorValue(NS_OK)
+ , mFileSystem(aFileSystem)
+{
+ MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+ aFileSystem->AssertIsOnOwningThread();
+}
+
+FileSystemTaskChildBase::~FileSystemTaskChildBase()
+{
+ mFileSystem->AssertIsOnOwningThread();
+}
+
+FileSystemBase*
+FileSystemTaskChildBase::GetFileSystem() const
+{
+ mFileSystem->AssertIsOnOwningThread();
+ return mFileSystem.get();
+}
+
+void
+FileSystemTaskChildBase::Start()
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ mozilla::ipc::PBackgroundChild* actor =
+ mozilla::ipc::BackgroundChild::GetForCurrentThread();
+ if (actor) {
+ ActorCreated(actor);
+ } else {
+ if (NS_WARN_IF(
+ !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) {
+ MOZ_CRASH();
+ }
+ }
+}
+
+void
+FileSystemTaskChildBase::ActorFailed()
+{
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+}
+
+void
+FileSystemTaskChildBase::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
+{
+ if (HasError()) {
+ // In this case we don't want to use IPC at all.
+ RefPtr<ErrorRunnable> runnable = new ErrorRunnable(this);
+ DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
+ return;
+ }
+
+ if (mFileSystem->IsShutdown()) {
+ return;
+ }
+
+ nsAutoString serialization;
+ mFileSystem->SerializeDOMPath(serialization);
+
+ ErrorResult rv;
+ FileSystemParams params = GetRequestParams(serialization, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return;
+ }
+
+ // Retain a reference so the task object isn't deleted without IPDL's
+ // knowledge. The reference will be released by
+ // mozilla::ipc::BackgroundChildImpl::DeallocPFileSystemRequestChild.
+ NS_ADDREF_THIS();
+
+ mozilla::ipc::PBackgroundChild* actor =
+ mozilla::ipc::BackgroundChild::GetForCurrentThread();
+ MOZ_ASSERT(actor);
+
+ actor->SendPFileSystemRequestConstructor(this, params);
+}
+
+void
+FileSystemTaskChildBase::SetRequestResult(const FileSystemResponseValue& aValue)
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (aValue.type() == FileSystemResponseValue::TFileSystemErrorResponse) {
+ FileSystemErrorResponse r = aValue;
+ mErrorValue = r.error();
+ } else {
+ ErrorResult rv;
+ SetSuccessRequestResult(aValue, rv);
+ mErrorValue = rv.StealNSResult();
+ }
+}
+
+bool
+FileSystemTaskChildBase::Recv__delete__(const FileSystemResponseValue& aValue)
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ SetRequestResult(aValue);
+ HandlerCallback();
+ return true;
+}
+
+void
+FileSystemTaskChildBase::SetError(const nsresult& aErrorValue)
+{
+ mErrorValue = FileSystemErrorFromNsError(aErrorValue);
+}
+
+/**
+ * FileSystemTaskParentBase class
+ */
+
+FileSystemTaskParentBase::FileSystemTaskParentBase(FileSystemBase* aFileSystem,
+ const FileSystemParams& aParam,
+ FileSystemRequestParent* aParent)
+ : mErrorValue(NS_OK)
+ , mFileSystem(aFileSystem)
+ , mRequestParent(aParent)
+ , mBackgroundEventTarget(NS_GetCurrentThread())
+{
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Only call from parent process!");
+ MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(mBackgroundEventTarget);
+ AssertIsOnBackgroundThread();
+}
+
+FileSystemTaskParentBase::~FileSystemTaskParentBase()
+{
+ // This task can be released on different threads because we dispatch it (as
+ // runnable) to main-thread, I/O and then back to the PBackground thread.
+ NS_ProxyRelease(mBackgroundEventTarget, mFileSystem.forget());
+ NS_ProxyRelease(mBackgroundEventTarget, mRequestParent.forget());
+}
+
+void
+FileSystemTaskParentBase::Start()
+{
+ AssertIsOnBackgroundThread();
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (NeedToGoToMainThread()) {
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
+ return;
+ }
+
+ DebugOnly<nsresult> rv = DispatchToIOThread(this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchToIOThread failed");
+}
+
+void
+FileSystemTaskParentBase::HandleResult()
+{
+ AssertIsOnBackgroundThread();
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (mFileSystem->IsShutdown()) {
+ return;
+ }
+
+ MOZ_ASSERT(mRequestParent);
+ Unused << mRequestParent->Send__delete__(mRequestParent, GetRequestResult());
+}
+
+FileSystemResponseValue
+FileSystemTaskParentBase::GetRequestResult() const
+{
+ AssertIsOnBackgroundThread();
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (HasError()) {
+ return FileSystemErrorResponse(mErrorValue);
+ }
+
+ ErrorResult rv;
+ FileSystemResponseValue value = GetSuccessRequestResult(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return FileSystemErrorResponse(rv.StealNSResult());
+ }
+
+ return value;
+}
+
+void
+FileSystemTaskParentBase::SetError(const nsresult& aErrorValue)
+{
+ mErrorValue = FileSystemErrorFromNsError(aErrorValue);
+}
+
+bool
+FileSystemTaskParentBase::NeedToGoToMainThread() const
+{
+ return mFileSystem->NeedToGoToMainThread();
+}
+
+nsresult
+FileSystemTaskParentBase::MainThreadWork()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mFileSystem->MainThreadWork();
+}
+
+NS_IMETHODIMP
+FileSystemTaskParentBase::Run()
+{
+ // This method can run in 3 different threads. Here why:
+ // 1. if we are on the main-thread it's because the task must do something
+ // here. If no errors are returned we go the step 2.
+ // 2. We can be here directly if the task doesn't have nothing to do on the
+ // main-thread. We are are on the I/O thread and we call IOWork().
+ // 3. Both step 1 (in case of error) and step 2 end up here where return the
+ // value back to the PBackground thread.
+ if (NS_IsMainThread()) {
+ MOZ_ASSERT(NeedToGoToMainThread());
+
+ nsresult rv = MainThreadWork();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SetError(rv);
+
+ // Something when wrong. Let's go to the Background thread directly
+ // skipping the I/O thread step.
+ rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Next step must happen on the I/O thread.
+ rv = DispatchToIOThread(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // Run I/O thread tasks
+ if (!IsOnBackgroundThread()) {
+ nsresult rv = IOWork();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SetError(rv);
+ }
+
+ // Let's go back to PBackground thread to finish the work.
+ rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // If we are here, it's because the I/O work has been done and we have to
+ // handle the result back via IPC.
+ AssertIsOnBackgroundThread();
+ HandleResult();
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/FileSystemTaskBase.h b/dom/filesystem/FileSystemTaskBase.h
new file mode 100644
index 000000000..9910f4c1f
--- /dev/null
+++ b/dom/filesystem/FileSystemTaskBase.h
@@ -0,0 +1,279 @@
+/* -*- 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_FileSystemTaskBase_h
+#define mozilla_dom_FileSystemTaskBase_h
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/PFileSystemRequestChild.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class FileSystemBase;
+class FileSystemParams;
+class PBlobParent;
+
+/*
+ * 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.
+ *
+ * The left block is the call sequence inside any process loading content, while
+ * the right block is the call sequence only inside the parent process.
+ *
+ * Page
+ * |
+ * | (1)
+ * ______|_________________________ | _________________________________
+ * | | | | | |
+ * | | | | | |
+ * | V | IPC | PBackground thread on |
+ * | [new FileSystemTaskChildBase()] | | | the parent process |
+ * | | | | | |
+ * | | (2) | | |
+ * | V | (3) | |
+ * | [GetRequestParams]------------------->[new FileSystemTaskParentBase()] |
+ * | | | | |
+ * | | | | | (4) _____________ |
+ * | | | | | | | |
+ * | | | | | | I/O Thread | |
+ * | | | | | | | |
+ * | | | | ---------> [IOWork] | |
+ * | | IPC | | | | |
+ * | | | | | | (5) | |
+ * | | | | -------------- | |
+ * | | | | | |_____________| |
+ * | | | | | |
+ * | | | | V |
+ * | | | | [HandleResult] |
+ * | | | | | |
+ * | | | | (6) |
+ * | | (7) | V |
+ * | [SetRequestResult]<---------------------[GetRequestResult] |
+ * | | | | |
+ * | | (8) | | | |
+ * | V | | | |
+ * |[HandlerCallback] | IPC | |
+ * |_______|_________________________| | |_________________________________|
+ * | |
+ * V
+ * Page
+ *
+ * 1. From the process that is handling the request
+ * Child/Parent (it can be in any process):
+ * (1) Call FileSystem API from content page with JS. Create a task and run.
+ * The base constructor [FileSystemTaskChildBase()] of the task should be
+ * called.
+ * (2) Forward the task to the parent process through the IPC and call
+ * [GetRequestParams] to prepare the parameters of the IPC.
+ * Parent:
+ * (3) The parent process receives IPC and handle it in
+ * FileystemRequestParent. Get the IPC parameters and create a task to run the
+ * IPC task.
+ * (4) The task operation will be performed in the member function of [IOWork].
+ * A I/O thread will be created to run that function. If error occurs
+ * during the operation, call [SetError] to record the error and then abort.
+ * (5) After finishing the task operation, call [HandleResult] to send the
+ * result back to the child process though the IPC.
+ * (6) Call [GetRequestResult] request result to prepare the parameters of the
+ * IPC. Because the formats of the error result for different task are the
+ * same, FileSystemTaskChildBase can handle the error message without
+ * interfering.
+ * Each task only needs to implement its specific success result preparation
+ * function -[GetSuccessRequestResult].
+ * Child/Parent:
+ * (7) The process receives IPC and calls [SetRequestResult] to get the
+ * task result. Each task needs to implement its specific success result
+ * parsing function [SetSuccessRequestResult] to get the success result.
+ * (8) Call [HandlerCallback] to send the task result to the content page.
+ */
+class FileSystemTaskChildBase : public PFileSystemRequestChild
+ , public nsIIPCBackgroundChildCreateCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+
+ /*
+ * Start the task. It will dispatch all the information to the parent process,
+ * PBackground thread. This method must be called from the owning thread.
+ */
+ void
+ Start();
+
+ /*
+ * The error codes are defined in xpcom/base/ErrorList.h and their
+ * corresponding error name and message are defined in dom/base/domerr.msg.
+ */
+ void
+ SetError(const nsresult& aErrorCode);
+
+ FileSystemBase*
+ GetFileSystem() const;
+
+ /*
+ * After the task is completed, this function will be called to pass the task
+ * result to the content page. This method is called in the owning thread.
+ * Override this function to handle the call back to the content page.
+ */
+ virtual void
+ HandlerCallback() = 0;
+
+ bool
+ HasError() const { return NS_FAILED(mErrorValue); }
+
+protected:
+ /*
+ * To create a task to handle the page content request.
+ */
+ explicit FileSystemTaskChildBase(FileSystemBase* aFileSystem);
+
+ virtual
+ ~FileSystemTaskChildBase();
+
+ /*
+ * Wrap the task parameter to FileSystemParams for sending it through IPC.
+ * It will be called when we need to forward a task from the child process to
+ * the parent process. This method runs in the owning thread.
+ * @param filesystem The string representation of the file system.
+ */
+ virtual FileSystemParams
+ GetRequestParams(const nsString& aSerializedDOMPath,
+ ErrorResult& aRv) const = 0;
+
+ /*
+ * Unwrap the IPC message to get the task success result.
+ * It will be called when the task is completed successfully and an IPC
+ * message is received in the child process and we want to get the task
+ * success result. This method runs in the owning thread.
+ */
+ virtual void
+ SetSuccessRequestResult(const FileSystemResponseValue& aValue,
+ ErrorResult& aRv) = 0;
+
+ // Overrides PFileSystemRequestChild
+ virtual bool
+ Recv__delete__(const FileSystemResponseValue& value) override;
+
+ nsresult mErrorValue;
+ RefPtr<FileSystemBase> mFileSystem;
+
+private:
+
+ /*
+ * Unwrap the IPC message to get the task result.
+ * It will be called when the task is completed and an IPC message is received
+ * in the content process and we want to get the task result. This runs on the
+ * owning thread.
+ */
+ void
+ SetRequestResult(const FileSystemResponseValue& aValue);
+};
+
+// This class is the 'alter ego' of FileSystemTaskChildBase in the PBackground
+// world.
+class FileSystemTaskParentBase : public Runnable
+{
+public:
+ /*
+ * Start the task. This must be called from the PBackground thread only.
+ */
+ void
+ Start();
+
+ /*
+ * The error codes are defined in xpcom/base/ErrorList.h and their
+ * corresponding error name and message are defined in dom/base/domerr.msg.
+ */
+ void
+ SetError(const nsresult& aErrorCode);
+
+ /*
+ * The function to perform task operation. It will be run on the I/O
+ * thread of the parent process.
+ * Overrides this function to define the task operation for individual task.
+ */
+ virtual nsresult
+ IOWork() = 0;
+
+ /*
+ * Wrap the task success result to FileSystemResponseValue for sending it
+ * through IPC. This method runs in the PBackground thread.
+ * It will be called when the task is completed successfully and we need to
+ * send the task success result back to the child process.
+ */
+ virtual FileSystemResponseValue
+ GetSuccessRequestResult(ErrorResult& aRv) const = 0;
+
+ /*
+ * After finishing the task operation, handle the task result.
+ * If it is an IPC task, send back the IPC result. It runs on the PBackground
+ * thread.
+ */
+ void
+ HandleResult();
+
+ // If this task must do something on the main-thread before IOWork(), it must
+ // overwrite this method. Otherwise it returns true if the FileSystem must be
+ // initialized on the main-thread. It's called from the Background thread.
+ virtual bool
+ NeedToGoToMainThread() const;
+
+ // This method is called only if NeedToGoToMainThread() returns true.
+ // Of course, it runs on the main-thread.
+ virtual nsresult
+ MainThreadWork();
+
+ bool
+ HasError() const { return NS_FAILED(mErrorValue); }
+
+ NS_IMETHOD
+ Run() override;
+
+ virtual nsresult
+ GetTargetPath(nsAString& aPath) const = 0;
+
+private:
+ /*
+ * Wrap the task result to FileSystemResponseValue for sending it through IPC.
+ * It will be called when the task is completed and we need to
+ * send the task result back to the content. This runs on the PBackground
+ * thread.
+ */
+ FileSystemResponseValue
+ GetRequestResult() const;
+
+protected:
+ /*
+ * To create a parent process task delivered from the child process through
+ * IPC.
+ */
+ FileSystemTaskParentBase(FileSystemBase* aFileSystem,
+ const FileSystemParams& aParam,
+ FileSystemRequestParent* aParent);
+
+ virtual
+ ~FileSystemTaskParentBase();
+
+ nsresult mErrorValue;
+ RefPtr<FileSystemBase> mFileSystem;
+ RefPtr<FileSystemRequestParent> mRequestParent;
+ nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemTaskBase_h
diff --git a/dom/filesystem/FileSystemUtils.cpp b/dom/filesystem/FileSystemUtils.cpp
new file mode 100644
index 000000000..872e049ee
--- /dev/null
+++ b/dom/filesystem/FileSystemUtils.cpp
@@ -0,0 +1,75 @@
+/* -*- 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 "mozilla/dom/FileSystemUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+bool
+TokenizerIgnoreNothing(char16_t /* aChar */)
+{
+ return false;
+}
+
+} // anonymous namespace
+
+/* static */ bool
+FileSystemUtils::IsDescendantPath(const nsAString& aPath,
+ const nsAString& aDescendantPath)
+{
+ // Check the sub-directory path to see if it has the parent path as prefix.
+ if (!aDescendantPath.Equals(aPath) &&
+ !StringBeginsWith(aDescendantPath, aPath)) {
+ return false;
+ }
+
+ return true;
+}
+
+/* static */ bool
+FileSystemUtils::IsValidRelativeDOMPath(const nsAString& aPath,
+ nsTArray<nsString>& aParts)
+{
+ // We don't allow empty relative path to access the root.
+ if (aPath.IsEmpty()) {
+ return false;
+ }
+
+ // Leading and trailing "/" are not allowed.
+ if (aPath.First() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR ||
+ aPath.Last() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR) {
+ return false;
+ }
+
+ NS_NAMED_LITERAL_STRING(kCurrentDir, ".");
+ NS_NAMED_LITERAL_STRING(kParentDir, "..");
+
+ // Split path and check each path component.
+ nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
+ tokenizer(aPath, FILESYSTEM_DOM_PATH_SEPARATOR_CHAR);
+
+ while (tokenizer.hasMoreTokens()) {
+ nsDependentSubstring pathComponent = tokenizer.nextToken();
+ // The path containing empty components, such as "foo//bar", is invalid.
+ // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar",
+ // to walk up the directory.
+ if (pathComponent.IsEmpty() ||
+ pathComponent.Equals(kCurrentDir) ||
+ pathComponent.Equals(kParentDir)) {
+ return false;
+ }
+
+ aParts.AppendElement(pathComponent);
+ }
+
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/FileSystemUtils.h b/dom/filesystem/FileSystemUtils.h
new file mode 100644
index 000000000..ae302e07c
--- /dev/null
+++ b/dom/filesystem/FileSystemUtils.h
@@ -0,0 +1,44 @@
+/* -*- 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_FileSystemUtils_h
+#define mozilla_dom_FileSystemUtils_h
+
+class nsIFile;
+
+namespace mozilla {
+namespace dom {
+
+#define FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL "/"
+#define FILESYSTEM_DOM_PATH_SEPARATOR_CHAR '/'
+
+/*
+ * This class is for error handling.
+ * All methods in this class are static.
+ */
+class FileSystemUtils
+{
+public:
+ /*
+ * Return true if aDescendantPath is a descendant of aPath.
+ */
+ static bool
+ IsDescendantPath(const nsAString& aPath,
+ const nsAString& aDescendantPath);
+
+ /**
+ * Return true if this is valid DOMPath. It also splits the path in
+ * subdirectories and stores them in aParts.
+ */
+ static bool
+ IsValidRelativeDOMPath(const nsAString& aPath,
+ nsTArray<nsString>& aParts);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemUtils_h
diff --git a/dom/filesystem/GetDirectoryListingTask.cpp b/dom/filesystem/GetDirectoryListingTask.cpp
new file mode 100644
index 000000000..e3ac2933f
--- /dev/null
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -0,0 +1,390 @@
+/* -*- 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 "GetDirectoryListingTask.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/UnionTypes.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsStringGlue.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * GetDirectoryListingTaskChild
+ */
+
+/* static */ already_AddRefed<GetDirectoryListingTaskChild>
+GetDirectoryListingTaskChild::Create(FileSystemBase* aFileSystem,
+ Directory* aDirectory,
+ nsIFile* aTargetPath,
+ const nsAString& aFilters,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aFileSystem);
+ MOZ_ASSERT(aDirectory);
+ aFileSystem->AssertIsOnOwningThread();
+
+ RefPtr<GetDirectoryListingTaskChild> task =
+ new GetDirectoryListingTaskChild(aFileSystem, aDirectory, aTargetPath,
+ aFilters);
+
+ // aTargetPath can be null. In this case SetError will be called.
+
+ nsCOMPtr<nsIGlobalObject> globalObject =
+ do_QueryInterface(aFileSystem->GetParentObject());
+ if (NS_WARN_IF(!globalObject)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ task->mPromise = Promise::Create(globalObject, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return task.forget();
+}
+
+GetDirectoryListingTaskChild::GetDirectoryListingTaskChild(FileSystemBase* aFileSystem,
+ Directory* aDirectory,
+ nsIFile* aTargetPath,
+ const nsAString& aFilters)
+ : FileSystemTaskChildBase(aFileSystem)
+ , mDirectory(aDirectory)
+ , mTargetPath(aTargetPath)
+ , mFilters(aFilters)
+{
+ MOZ_ASSERT(aFileSystem);
+ aFileSystem->AssertIsOnOwningThread();
+}
+
+GetDirectoryListingTaskChild::~GetDirectoryListingTaskChild()
+{
+ mFileSystem->AssertIsOnOwningThread();
+}
+
+already_AddRefed<Promise>
+GetDirectoryListingTaskChild::GetPromise()
+{
+ mFileSystem->AssertIsOnOwningThread();
+ return RefPtr<Promise>(mPromise).forget();
+}
+
+FileSystemParams
+GetDirectoryListingTaskChild::GetRequestParams(const nsString& aSerializedDOMPath,
+ ErrorResult& aRv) const
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ // this is the real path.
+ nsAutoString path;
+ aRv = mTargetPath->GetPath(path);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return FileSystemGetDirectoryListingParams();
+ }
+
+ // this is the dom path.
+ nsAutoString directoryPath;
+ mDirectory->GetPath(directoryPath, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return FileSystemGetDirectoryListingParams();
+ }
+
+ return FileSystemGetDirectoryListingParams(aSerializedDOMPath, path,
+ directoryPath, mFilters);
+}
+
+void
+GetDirectoryListingTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue,
+ ErrorResult& aRv)
+{
+ mFileSystem->AssertIsOnOwningThread();
+ MOZ_ASSERT(aValue.type() ==
+ FileSystemResponseValue::TFileSystemDirectoryListingResponse);
+
+ FileSystemDirectoryListingResponse r = aValue;
+ for (uint32_t i = 0; i < r.data().Length(); ++i) {
+ const FileSystemDirectoryListingResponseData& data = r.data()[i];
+
+ OwningFileOrDirectory* ofd = mTargetData.AppendElement(fallible);
+ if (!ofd) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseFile) {
+ const FileSystemDirectoryListingResponseFile& d =
+ data.get_FileSystemDirectoryListingResponseFile();
+
+ RefPtr<BlobImpl> blobImpl =
+ static_cast<BlobChild*>(d.blobChild())->GetBlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ RefPtr<File> file = File::Create(mFileSystem->GetParentObject(), blobImpl);
+ MOZ_ASSERT(file);
+
+ ofd->SetAsFile() = file;
+ } else {
+ MOZ_ASSERT(data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseDirectory);
+ const FileSystemDirectoryListingResponseDirectory& d =
+ data.get_FileSystemDirectoryListingResponseDirectory();
+
+ nsCOMPtr<nsIFile> path;
+ aRv = NS_NewLocalFile(d.directoryRealPath(), true, getter_AddRefs(path));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RefPtr<Directory> directory =
+ Directory::Create(mFileSystem->GetParentObject(), path, mFileSystem);
+ MOZ_ASSERT(directory);
+
+ ofd->SetAsDirectory() = directory;
+ }
+ }
+}
+
+void
+GetDirectoryListingTaskChild::HandlerCallback()
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (mFileSystem->IsShutdown()) {
+ mPromise = nullptr;
+ return;
+ }
+
+ if (HasError()) {
+ mPromise->MaybeReject(mErrorValue);
+ mPromise = nullptr;
+ return;
+ }
+
+ mPromise->MaybeResolve(mTargetData);
+ mPromise = nullptr;
+}
+
+/**
+ * GetDirectoryListingTaskParent
+ */
+
+/* static */ already_AddRefed<GetDirectoryListingTaskParent>
+GetDirectoryListingTaskParent::Create(FileSystemBase* aFileSystem,
+ const FileSystemGetDirectoryListingParams& aParam,
+ FileSystemRequestParent* aParent,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileSystem);
+
+ RefPtr<GetDirectoryListingTaskParent> task =
+ new GetDirectoryListingTaskParent(aFileSystem, aParam, aParent);
+
+ aRv = NS_NewLocalFile(aParam.realPath(), true,
+ getter_AddRefs(task->mTargetPath));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return task.forget();
+}
+
+GetDirectoryListingTaskParent::GetDirectoryListingTaskParent(FileSystemBase* aFileSystem,
+ const FileSystemGetDirectoryListingParams& aParam,
+ FileSystemRequestParent* aParent)
+ : FileSystemTaskParentBase(aFileSystem, aParam, aParent)
+ , mDOMPath(aParam.domPath())
+ , mFilters(aParam.filters())
+{
+ MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileSystem);
+}
+
+FileSystemResponseValue
+GetDirectoryListingTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const
+{
+ AssertIsOnBackgroundThread();
+
+ InfallibleTArray<PBlobParent*> blobs;
+
+ nsTArray<FileSystemDirectoryListingResponseData> inputs;
+
+ for (unsigned i = 0; i < mTargetData.Length(); i++) {
+ if (mTargetData[i].mType == FileOrDirectoryPath::eFilePath) {
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = NS_NewLocalFile(mTargetData[i].mPath, true,
+ getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return FileSystemErrorResponse(rv);
+ }
+
+ FileSystemDirectoryListingResponseFile fileData;
+ RefPtr<BlobImpl> blobImpl = new BlobImplFile(path);
+
+ nsAutoString filePath;
+ filePath.Assign(mDOMPath);
+
+ // This is specific for unix root filesystem.
+ if (!mDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
+ filePath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ }
+
+ nsAutoString name;
+ blobImpl->GetName(name);
+ filePath.Append(name);
+ blobImpl->SetDOMPath(filePath);
+
+ fileData.blobParent() =
+ BlobParent::GetOrCreate(mRequestParent->Manager(), blobImpl);
+ inputs.AppendElement(fileData);
+ } else {
+ MOZ_ASSERT(mTargetData[i].mType == FileOrDirectoryPath::eDirectoryPath);
+ FileSystemDirectoryListingResponseDirectory directoryData;
+ directoryData.directoryRealPath() = mTargetData[i].mPath;
+ inputs.AppendElement(directoryData);
+ }
+ }
+
+ FileSystemDirectoryListingResponse response;
+ response.data().SwapElements(inputs);
+ return response;
+}
+
+nsresult
+GetDirectoryListingTaskParent::IOWork()
+{
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Only call from parent process!");
+ MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker 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) {
+ if (!mFileSystem->ShouldCreateDirectory()) {
+ return NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
+ }
+
+ rv = mTargetPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Get isDirectory.
+ bool isDir;
+ rv = mTargetPath->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 = mTargetPath->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool filterOutSensitive = false;
+ {
+ HTMLSplitOnSpacesTokenizer tokenizer(mFilters, ';');
+ nsAutoString token;
+ while (tokenizer.hasMoreTokens()) {
+ token = tokenizer.nextToken();
+ if (token.EqualsLiteral("filter-out-sensitive")) {
+ filterOutSensitive = true;
+ } else {
+ MOZ_CRASH("Unrecognized filter");
+ }
+ }
+ }
+
+ 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 isSpecial, isFile;
+ if (NS_WARN_IF(NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+ isSpecial) {
+ continue;
+ }
+ if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
+ NS_FAILED(currFile->IsDirectory(&isDir))) ||
+ !(isFile || isDir)) {
+ continue;
+ }
+
+ if (filterOutSensitive) {
+ bool isHidden;
+ if (NS_WARN_IF(NS_FAILED(currFile->IsHidden(&isHidden))) || isHidden) {
+ continue;
+ }
+ nsAutoString leafName;
+ if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
+ continue;
+ }
+ if (leafName[0] == char16_t('.')) {
+ continue;
+ }
+ }
+
+ nsAutoString path;
+ if (NS_WARN_IF(NS_FAILED(currFile->GetPath(path)))) {
+ continue;
+ }
+
+ FileOrDirectoryPath element;
+ element.mPath = path;
+ element.mType = isDir ? FileOrDirectoryPath::eDirectoryPath
+ : FileOrDirectoryPath::eFilePath;
+
+ if (!mTargetData.AppendElement(element, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+GetDirectoryListingTaskParent::GetTargetPath(nsAString& aPath) const
+{
+ return mTargetPath->GetPath(aPath);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/GetDirectoryListingTask.h b/dom/filesystem/GetDirectoryListingTask.h
new file mode 100644
index 000000000..7692c8bae
--- /dev/null
+++ b/dom/filesystem/GetDirectoryListingTask.h
@@ -0,0 +1,106 @@
+/* -*- 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_GetDirectoryListing_h
+#define mozilla_dom_GetDirectoryListing_h
+
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemTaskBase.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class FileSystemGetDirectoryListingParams;
+class OwningFileOrDirectory;
+
+class GetDirectoryListingTaskChild final : public FileSystemTaskChildBase
+{
+public:
+ static already_AddRefed<GetDirectoryListingTaskChild>
+ Create(FileSystemBase* aFileSystem,
+ Directory* aDirectory,
+ nsIFile* aTargetPath,
+ const nsAString& aFilters,
+ ErrorResult& aRv);
+
+ virtual
+ ~GetDirectoryListingTaskChild();
+
+ already_AddRefed<Promise>
+ GetPromise();
+
+private:
+ // If aDirectoryOnly is set, we should ensure that the target is a directory.
+ GetDirectoryListingTaskChild(FileSystemBase* aFileSystem,
+ Directory* aDirectory,
+ nsIFile* aTargetPath,
+ const nsAString& aFilters);
+
+ 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;
+ RefPtr<Directory> mDirectory;
+ nsCOMPtr<nsIFile> mTargetPath;
+ nsString mFilters;
+
+ FallibleTArray<OwningFileOrDirectory> mTargetData;
+};
+
+class GetDirectoryListingTaskParent final : public FileSystemTaskParentBase
+{
+public:
+ static already_AddRefed<GetDirectoryListingTaskParent>
+ Create(FileSystemBase* aFileSystem,
+ const FileSystemGetDirectoryListingParams& aParam,
+ FileSystemRequestParent* aParent,
+ ErrorResult& aRv);
+
+ nsresult
+ GetTargetPath(nsAString& aPath) const override;
+
+private:
+ GetDirectoryListingTaskParent(FileSystemBase* aFileSystem,
+ const FileSystemGetDirectoryListingParams& aParam,
+ FileSystemRequestParent* aParent);
+
+ virtual FileSystemResponseValue
+ GetSuccessRequestResult(ErrorResult& aRv) const override;
+
+ virtual nsresult
+ IOWork() override;
+
+ nsCOMPtr<nsIFile> mTargetPath;
+ nsString mDOMPath;
+ nsString mFilters;
+
+ struct FileOrDirectoryPath
+ {
+ nsString mPath;
+
+ enum {
+ eFilePath,
+ eDirectoryPath
+ } mType;
+ };
+
+ FallibleTArray<FileOrDirectoryPath> mTargetData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GetDirectoryListing_h
diff --git a/dom/filesystem/GetFileOrDirectoryTask.cpp b/dom/filesystem/GetFileOrDirectoryTask.cpp
new file mode 100644
index 000000000..f71976503
--- /dev/null
+++ b/dom/filesystem/GetFileOrDirectoryTask.cpp
@@ -0,0 +1,280 @@
+/* -*- 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 "GetFileOrDirectoryTask.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 {
+
+/**
+ * GetFileOrDirectoryTaskChild
+ */
+
+/* static */ already_AddRefed<GetFileOrDirectoryTaskChild>
+GetFileOrDirectoryTaskChild::Create(FileSystemBase* aFileSystem,
+ nsIFile* aTargetPath,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+ MOZ_ASSERT(aFileSystem);
+
+ RefPtr<GetFileOrDirectoryTaskChild> task =
+ new GetFileOrDirectoryTaskChild(aFileSystem, aTargetPath);
+
+ // aTargetPath can be null. In this case SetError will be called.
+
+ nsCOMPtr<nsIGlobalObject> globalObject =
+ do_QueryInterface(aFileSystem->GetParentObject());
+ if (NS_WARN_IF(!globalObject)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ task->mPromise = Promise::Create(globalObject, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return task.forget();
+}
+
+GetFileOrDirectoryTaskChild::GetFileOrDirectoryTaskChild(FileSystemBase* aFileSystem,
+ nsIFile* aTargetPath)
+ : FileSystemTaskChildBase(aFileSystem)
+ , mTargetPath(aTargetPath)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+ MOZ_ASSERT(aFileSystem);
+}
+
+GetFileOrDirectoryTaskChild::~GetFileOrDirectoryTaskChild()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+already_AddRefed<Promise>
+GetFileOrDirectoryTaskChild::GetPromise()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+ return RefPtr<Promise>(mPromise).forget();
+}
+
+FileSystemParams
+GetFileOrDirectoryTaskChild::GetRequestParams(const nsString& aSerializedDOMPath,
+ ErrorResult& aRv) const
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+
+ nsAutoString path;
+ aRv = mTargetPath->GetPath(path);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return FileSystemGetFileOrDirectoryParams();
+ }
+
+ return FileSystemGetFileOrDirectoryParams(aSerializedDOMPath, path);
+}
+
+void
+GetFileOrDirectoryTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+ switch (aValue.type()) {
+ case FileSystemResponseValue::TFileSystemFileResponse: {
+ FileSystemFileResponse r = aValue;
+
+ RefPtr<BlobImpl> blobImpl =
+ static_cast<BlobChild*>(r.blobChild())->GetBlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ mResultFile = File::Create(mFileSystem->GetParentObject(), blobImpl);
+ MOZ_ASSERT(mResultFile);
+ break;
+ }
+ case FileSystemResponseValue::TFileSystemDirectoryResponse: {
+ FileSystemDirectoryResponse r = aValue;
+
+ nsCOMPtr<nsIFile> file;
+ aRv = NS_NewLocalFile(r.realPath(), true, getter_AddRefs(file));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ mResultDirectory = Directory::Create(mFileSystem->GetParentObject(),
+ file, mFileSystem);
+ MOZ_ASSERT(mResultDirectory);
+ break;
+ }
+ default: {
+ NS_RUNTIMEABORT("not reached");
+ break;
+ }
+ }
+}
+
+void
+GetFileOrDirectoryTaskChild::HandlerCallback()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+ if (mFileSystem->IsShutdown()) {
+ mPromise = nullptr;
+ return;
+ }
+
+ if (HasError()) {
+ mPromise->MaybeReject(mErrorValue);
+ mPromise = nullptr;
+ return;
+ }
+
+ if (mResultDirectory) {
+ mPromise->MaybeResolve(mResultDirectory);
+ mResultDirectory = nullptr;
+ mPromise = nullptr;
+ return;
+ }
+
+ MOZ_ASSERT(mResultFile);
+ mPromise->MaybeResolve(mResultFile);
+ mResultFile = nullptr;
+ mPromise = nullptr;
+}
+
+/**
+ * GetFileOrDirectoryTaskParent
+ */
+
+/* static */ already_AddRefed<GetFileOrDirectoryTaskParent>
+GetFileOrDirectoryTaskParent::Create(FileSystemBase* aFileSystem,
+ const FileSystemGetFileOrDirectoryParams& aParam,
+ FileSystemRequestParent* aParent,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileSystem);
+
+ RefPtr<GetFileOrDirectoryTaskParent> task =
+ new GetFileOrDirectoryTaskParent(aFileSystem, aParam, aParent);
+
+ aRv = NS_NewLocalFile(aParam.realPath(), true,
+ getter_AddRefs(task->mTargetPath));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return task.forget();
+}
+
+GetFileOrDirectoryTaskParent::GetFileOrDirectoryTaskParent(FileSystemBase* aFileSystem,
+ const FileSystemGetFileOrDirectoryParams& aParam,
+ FileSystemRequestParent* aParent)
+ : FileSystemTaskParentBase(aFileSystem, aParam, aParent)
+ , mIsDirectory(false)
+{
+ MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileSystem);
+}
+
+FileSystemResponseValue
+GetFileOrDirectoryTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const
+{
+ AssertIsOnBackgroundThread();
+
+ nsAutoString path;
+ aRv = mTargetPath->GetPath(path);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return FileSystemDirectoryResponse();
+ }
+
+ if (mIsDirectory) {
+ return FileSystemDirectoryResponse(path);
+ }
+
+ RefPtr<BlobImpl> blobImpl = new BlobImplFile(mTargetPath);
+ BlobParent* blobParent =
+ BlobParent::GetOrCreate(mRequestParent->Manager(), blobImpl);
+ return FileSystemFileResponse(blobParent, nullptr);
+}
+
+nsresult
+GetFileOrDirectoryTaskParent::IOWork()
+{
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Only call from parent process!");
+ MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!");
+
+ if (mFileSystem->IsShutdown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Whether we want to get the root directory.
+ bool exists;
+ nsresult rv = mTargetPath->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ if (!mFileSystem->ShouldCreateDirectory()) {
+ return NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
+ }
+
+ rv = mTargetPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Get isDirectory.
+ rv = mTargetPath->IsDirectory(&mIsDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mIsDirectory) {
+ return NS_OK;
+ }
+
+ bool isFile;
+ // Get isFile
+ rv = mTargetPath->IsFile(&isFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isFile) {
+ // Neither directory or file.
+ return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+ }
+
+ if (!mFileSystem->IsSafeFile(mTargetPath)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GetFileOrDirectoryTaskParent::GetTargetPath(nsAString& aPath) const
+{
+ return mTargetPath->GetPath(aPath);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/GetFileOrDirectoryTask.h b/dom/filesystem/GetFileOrDirectoryTask.h
new file mode 100644
index 000000000..2a53df158
--- /dev/null
+++ b/dom/filesystem/GetFileOrDirectoryTask.h
@@ -0,0 +1,89 @@
+/* -*- 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_GetFileOrDirectory_h
+#define mozilla_dom_GetFileOrDirectory_h
+
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemTaskBase.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class FileSystemGetFileOrDirectoryParams;
+
+class GetFileOrDirectoryTaskChild final : public FileSystemTaskChildBase
+{
+public:
+ static already_AddRefed<GetFileOrDirectoryTaskChild>
+ Create(FileSystemBase* aFileSystem,
+ nsIFile* aTargetPath,
+ ErrorResult& aRv);
+
+ virtual
+ ~GetFileOrDirectoryTaskChild();
+
+ already_AddRefed<Promise>
+ GetPromise();
+
+protected:
+ virtual FileSystemParams
+ GetRequestParams(const nsString& aSerializedDOMPath,
+ ErrorResult& aRv) const override;
+
+ virtual void
+ SetSuccessRequestResult(const FileSystemResponseValue& aValue,
+ ErrorResult& aRv) override;
+ virtual void
+ HandlerCallback() override;
+
+private:
+ GetFileOrDirectoryTaskChild(FileSystemBase* aFileSystem,
+ nsIFile* aTargetPath);
+
+ RefPtr<Promise> mPromise;
+ nsCOMPtr<nsIFile> mTargetPath;
+
+ RefPtr<File> mResultFile;
+ RefPtr<Directory> mResultDirectory;
+};
+
+class GetFileOrDirectoryTaskParent final : public FileSystemTaskParentBase
+{
+public:
+ static already_AddRefed<GetFileOrDirectoryTaskParent>
+ Create(FileSystemBase* aFileSystem,
+ const FileSystemGetFileOrDirectoryParams& aParam,
+ FileSystemRequestParent* aParent,
+ ErrorResult& aRv);
+
+ nsresult
+ GetTargetPath(nsAString& aPath) const override;
+
+protected:
+ virtual FileSystemResponseValue
+ GetSuccessRequestResult(ErrorResult& aRv) const override;
+
+ virtual nsresult
+ IOWork() override;
+
+private:
+ GetFileOrDirectoryTaskParent(FileSystemBase* aFileSystem,
+ const FileSystemGetFileOrDirectoryParams& aParam,
+ FileSystemRequestParent* aParent);
+
+ nsCOMPtr<nsIFile> mTargetPath;
+
+ // Whether we get a directory.
+ bool mIsDirectory;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GetFileOrDirectory_h
diff --git a/dom/filesystem/GetFilesHelper.cpp b/dom/filesystem/GetFilesHelper.cpp
new file mode 100644
index 000000000..21d228a60
--- /dev/null
+++ b/dom/filesystem/GetFilesHelper.cpp
@@ -0,0 +1,643 @@
+/* -*- 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 "GetFilesHelper.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// This class is used in the DTOR of GetFilesHelper to release resources in the
+// correct thread.
+class ReleaseRunnable final : public Runnable
+{
+public:
+ static void
+ MaybeReleaseOnMainThread(nsTArray<RefPtr<Promise>>& aPromises,
+ nsTArray<RefPtr<GetFilesCallback>>& aCallbacks,
+ Sequence<RefPtr<File>>& aFiles,
+ already_AddRefed<nsIGlobalObject> aGlobal)
+ {
+ nsCOMPtr<nsIGlobalObject> global(aGlobal);
+ if (NS_IsMainThread()) {
+ return;
+ }
+
+ RefPtr<ReleaseRunnable> runnable =
+ new ReleaseRunnable(aPromises, aCallbacks, aFiles, global.forget());
+ NS_DispatchToMainThread(runnable);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mPromises.Clear();
+ mCallbacks.Clear();
+ mFiles.Clear();
+ mGlobal = nullptr;
+
+ return NS_OK;
+ }
+
+private:
+ ReleaseRunnable(nsTArray<RefPtr<Promise>>& aPromises,
+ nsTArray<RefPtr<GetFilesCallback>>& aCallbacks,
+ Sequence<RefPtr<File>>& aFiles,
+ already_AddRefed<nsIGlobalObject> aGlobal)
+ {
+ mPromises.SwapElements(aPromises);
+ mCallbacks.SwapElements(aCallbacks);
+ mFiles.SwapElements(aFiles);
+ mGlobal = aGlobal;
+ }
+
+ nsTArray<RefPtr<Promise>> mPromises;
+ nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
+ Sequence<RefPtr<File>> mFiles;
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+};
+
+} // anonymous
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelper Base class
+
+already_AddRefed<GetFilesHelper>
+GetFilesHelper::Create(nsIGlobalObject* aGlobal,
+ const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
+ bool aRecursiveFlag, ErrorResult& aRv)
+{
+ RefPtr<GetFilesHelper> helper;
+
+ if (XRE_IsParentProcess()) {
+ helper = new GetFilesHelper(aGlobal, aRecursiveFlag);
+ } else {
+ helper = new GetFilesHelperChild(aGlobal, aRecursiveFlag);
+ }
+
+ nsAutoString directoryPath;
+
+ for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
+ const OwningFileOrDirectory& data = aFilesOrDirectory[i];
+ if (data.IsFile()) {
+ if (!helper->mFiles.AppendElement(data.GetAsFile(), fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ } else {
+ MOZ_ASSERT(data.IsDirectory());
+
+ // We support the upload of only 1 top-level directory from our
+ // directory picker. This means that we cannot have more than 1
+ // Directory object in aFilesOrDirectory array.
+ MOZ_ASSERT(directoryPath.IsEmpty());
+
+ RefPtr<Directory> directory = data.GetAsDirectory();
+ MOZ_ASSERT(directory);
+
+ aRv = directory->GetFullRealPath(directoryPath);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+ }
+
+ // No directories to explore.
+ if (directoryPath.IsEmpty()) {
+ helper->mListingCompleted = true;
+ return helper.forget();
+ }
+
+ MOZ_ASSERT(helper->mFiles.IsEmpty());
+ helper->SetDirectoryPath(directoryPath);
+
+ helper->Work(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return helper.forget();
+}
+
+GetFilesHelper::GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
+ : GetFilesHelperBase(aRecursiveFlag)
+ , mGlobal(aGlobal)
+ , mListingCompleted(false)
+ , mErrorResult(NS_OK)
+ , mMutex("GetFilesHelper::mMutex")
+ , mCanceled(false)
+{
+}
+
+GetFilesHelper::~GetFilesHelper()
+{
+ ReleaseRunnable::MaybeReleaseOnMainThread(mPromises, mCallbacks, mFiles,
+ mGlobal.forget());
+}
+
+void
+GetFilesHelper::AddPromise(Promise* aPromise)
+{
+ MOZ_ASSERT(aPromise);
+
+ // Still working.
+ if (!mListingCompleted) {
+ mPromises.AppendElement(aPromise);
+ return;
+ }
+
+ MOZ_ASSERT(mPromises.IsEmpty());
+ ResolveOrRejectPromise(aPromise);
+}
+
+void
+GetFilesHelper::AddCallback(GetFilesCallback* aCallback)
+{
+ MOZ_ASSERT(aCallback);
+
+ // Still working.
+ if (!mListingCompleted) {
+ mCallbacks.AppendElement(aCallback);
+ return;
+ }
+
+ MOZ_ASSERT(mCallbacks.IsEmpty());
+ RunCallback(aCallback);
+}
+
+void
+GetFilesHelper::Unlink()
+{
+ mGlobal = nullptr;
+ mFiles.Clear();
+ mPromises.Clear();
+ mCallbacks.Clear();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mCanceled = true;
+ }
+
+ Cancel();
+}
+
+void
+GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+ GetFilesHelper* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
+}
+
+void
+GetFilesHelper::Work(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+
+ aRv = target->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+GetFilesHelper::Run()
+{
+ MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+ MOZ_ASSERT(!mListingCompleted);
+
+ // First step is to retrieve the list of file paths.
+ // This happens in the I/O thread.
+ if (!NS_IsMainThread()) {
+ RunIO();
+
+ // If this operation has been canceled, we don't have to go back to
+ // main-thread.
+ if (IsCanceled()) {
+ return NS_OK;
+ }
+
+ return NS_DispatchToMainThread(this);
+ }
+
+ // We are here, but we should not do anything on this thread because, in the
+ // meantime, the operation has been canceled.
+ if (IsCanceled()) {
+ return NS_OK;
+ }
+
+ RunMainThread();
+
+ OperationCompleted();
+ return NS_OK;
+}
+
+void
+GetFilesHelper::OperationCompleted()
+{
+ // We mark the operation as completed here.
+ mListingCompleted = true;
+
+ // Let's process the pending promises.
+ nsTArray<RefPtr<Promise>> promises;
+ promises.SwapElements(mPromises);
+
+ for (uint32_t i = 0; i < promises.Length(); ++i) {
+ ResolveOrRejectPromise(promises[i]);
+ }
+
+ // Let's process the pending callbacks.
+ nsTArray<RefPtr<GetFilesCallback>> callbacks;
+ callbacks.SwapElements(mCallbacks);
+
+ for (uint32_t i = 0; i < callbacks.Length(); ++i) {
+ RunCallback(callbacks[i]);
+ }
+}
+
+void
+GetFilesHelper::RunIO()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+ MOZ_ASSERT(!mListingCompleted);
+
+ nsCOMPtr<nsIFile> file;
+ mErrorResult = NS_NewLocalFile(mDirectoryPath, true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+ return;
+ }
+
+ nsAutoString leafName;
+ mErrorResult = file->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+ return;
+ }
+
+ nsAutoString domPath;
+ domPath.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ domPath.Append(leafName);
+
+ mErrorResult = ExploreDirectory(domPath, file);
+}
+
+void
+GetFilesHelper::RunMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+ MOZ_ASSERT(!mListingCompleted);
+
+ // If there is an error, do nothing.
+ if (NS_FAILED(mErrorResult)) {
+ return;
+ }
+
+ // Create the sequence of Files.
+ for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) {
+ RefPtr<File> domFile = File::Create(mGlobal, mTargetBlobImplArray[i]);
+ MOZ_ASSERT(domFile);
+
+ if (!mFiles.AppendElement(domFile, fallible)) {
+ mErrorResult = NS_ERROR_OUT_OF_MEMORY;
+ mFiles.Clear();
+ return;
+ }
+ }
+}
+
+nsresult
+GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aFile);
+
+ // We check if this operation has to be terminated at each recursion.
+ if (IsCanceled()) {
+ return NS_OK;
+ }
+
+ nsresult rv = AddExploredDirectory(aFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aFile->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, isDir;
+ if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
+ NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+ isSpecial) {
+ continue;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
+ NS_FAILED(currFile->IsDirectory(&isDir))) ||
+ !(isFile || isDir)) {
+ continue;
+ }
+
+ // We don't want to explore loops of links.
+ if (isDir && isLink && !ShouldFollowSymLink(currFile)) {
+ continue;
+ }
+
+ // The new domPath
+ nsAutoString domPath;
+ domPath.Assign(aDOMPath);
+ if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
+ domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ }
+
+ nsAutoString leafName;
+ if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
+ continue;
+ }
+ domPath.Append(leafName);
+
+ if (isFile) {
+ RefPtr<BlobImpl> blobImpl = new BlobImplFile(currFile);
+ blobImpl->SetDOMPath(domPath);
+
+ if (!mTargetBlobImplArray.AppendElement(blobImpl, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ continue;
+ }
+
+ MOZ_ASSERT(isDir);
+ if (!mRecursiveFlag) {
+ continue;
+ }
+
+ // Recursive.
+ rv = ExploreDirectory(domPath, currFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GetFilesHelperBase::AddExploredDirectory(nsIFile* aDir)
+{
+ nsresult rv;
+
+#ifdef DEBUG
+ bool isDir;
+ rv = aDir->IsDirectory(&isDir);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(isDir, "Why are we here?");
+#endif
+
+ bool isLink;
+ rv = aDir->IsSymlink(&isLink);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString path;
+
+ if (!isLink) {
+ nsAutoString path16;
+ rv = aDir->GetPath(path16);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ path = NS_ConvertUTF16toUTF8(path16);
+ } else {
+ rv = aDir->GetNativeTarget(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mExploredDirectories.PutEntry(path);
+ return NS_OK;
+}
+
+bool
+GetFilesHelperBase::ShouldFollowSymLink(nsIFile* aDir)
+{
+#ifdef DEBUG
+ bool isLink, isDir;
+ if (NS_WARN_IF(NS_FAILED(aDir->IsSymlink(&isLink)) ||
+ NS_FAILED(aDir->IsDirectory(&isDir)))) {
+ return false;
+ }
+
+ MOZ_ASSERT(isLink && isDir, "Why are we here?");
+#endif
+
+ nsAutoCString targetPath;
+ if (NS_WARN_IF(NS_FAILED(aDir->GetNativeTarget(targetPath)))) {
+ return false;
+ }
+
+ return !mExploredDirectories.Contains(targetPath);
+}
+
+void
+GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListingCompleted);
+ MOZ_ASSERT(aPromise);
+
+ // Error propagation.
+ if (NS_FAILED(mErrorResult)) {
+ aPromise->MaybeReject(mErrorResult);
+ return;
+ }
+
+ aPromise->MaybeResolve(mFiles);
+}
+
+void
+GetFilesHelper::RunCallback(GetFilesCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListingCompleted);
+ MOZ_ASSERT(aCallback);
+
+ aCallback->Callback(mErrorResult, mFiles);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelperChild class
+
+void
+GetFilesHelperChild::Work(ErrorResult& aRv)
+{
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (NS_WARN_IF(!cc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aRv = nsContentUtils::GenerateUUIDInPlace(mUUID);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ mPendingOperation = true;
+ cc->CreateGetFilesRequest(mDirectoryPath, mRecursiveFlag, mUUID, this);
+}
+
+void
+GetFilesHelperChild::Cancel()
+{
+ if (!mPendingOperation) {
+ return;
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (NS_WARN_IF(!cc)) {
+ return;
+ }
+
+ mPendingOperation = false;
+ cc->DeleteGetFilesRequest(mUUID, this);
+}
+
+bool
+GetFilesHelperChild::AppendBlobImpl(BlobImpl* aBlobImpl)
+{
+ MOZ_ASSERT(mPendingOperation);
+ MOZ_ASSERT(aBlobImpl);
+ MOZ_ASSERT(aBlobImpl->IsFile());
+
+ RefPtr<File> file = File::Create(mGlobal, aBlobImpl);
+ MOZ_ASSERT(file);
+
+ return mFiles.AppendElement(file, fallible);
+}
+
+void
+GetFilesHelperChild::Finished(nsresult aError)
+{
+ MOZ_ASSERT(mPendingOperation);
+ MOZ_ASSERT(NS_SUCCEEDED(mErrorResult));
+
+ mPendingOperation = false;
+ mErrorResult = aError;
+
+ OperationCompleted();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelperParent class
+
+class GetFilesHelperParentCallback final : public GetFilesCallback
+{
+public:
+ explicit GetFilesHelperParentCallback(GetFilesHelperParent* aParent)
+ : mParent(aParent)
+ {
+ MOZ_ASSERT(aParent);
+ }
+
+ void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
+ {
+ if (NS_FAILED(aStatus)) {
+ mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+ GetFilesResponseFailure(aStatus));
+ return;
+ }
+
+ GetFilesResponseSuccess success;
+ nsTArray<PBlobParent*>& blobsParent = success.blobsParent();
+ blobsParent.SetLength(aFiles.Length());
+
+ for (uint32_t i = 0; i < aFiles.Length(); ++i) {
+ blobsParent[i] =
+ mParent->mContentParent->GetOrCreateActorForBlob(aFiles[i]);
+ if (!blobsParent[i]) {
+ mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+ GetFilesResponseFailure(NS_ERROR_OUT_OF_MEMORY));
+ return;
+ }
+ }
+
+ mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+ success);
+ }
+
+private:
+ // Raw pointer because this callback is kept alive by this parent object.
+ GetFilesHelperParent* mParent;
+};
+
+GetFilesHelperParent::GetFilesHelperParent(const nsID& aUUID,
+ ContentParent* aContentParent,
+ bool aRecursiveFlag)
+ : GetFilesHelper(nullptr, aRecursiveFlag)
+ , mContentParent(aContentParent)
+ , mUUID(aUUID)
+{}
+
+GetFilesHelperParent::~GetFilesHelperParent()
+{
+ NS_ReleaseOnMainThread(mContentParent.forget());
+}
+
+/* static */ already_AddRefed<GetFilesHelperParent>
+GetFilesHelperParent::Create(const nsID& aUUID, const nsAString& aDirectoryPath,
+ bool aRecursiveFlag, ContentParent* aContentParent,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aContentParent);
+
+ RefPtr<GetFilesHelperParent> helper =
+ new GetFilesHelperParent(aUUID, aContentParent, aRecursiveFlag);
+ helper->SetDirectoryPath(aDirectoryPath);
+
+ helper->Work(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<GetFilesHelperParentCallback> callback =
+ new GetFilesHelperParentCallback(helper);
+ helper->AddCallback(callback);
+
+ return helper.forget();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/GetFilesHelper.h b/dom/filesystem/GetFilesHelper.h
new file mode 100644
index 000000000..a1bb1b27a
--- /dev/null
+++ b/dom/filesystem/GetFilesHelper.h
@@ -0,0 +1,205 @@
+/* -*- 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_GetFilesHelper_h
+#define mozilla_dom_GetFilesHelper_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class ContentParent;
+class File;
+class GetFilesHelperParent;
+class OwningFileOrDirectory;
+class Promise;
+
+class GetFilesCallback
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GetFilesCallback);
+
+ virtual void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
+
+protected:
+ virtual ~GetFilesCallback() {}
+};
+
+class GetFilesHelperBase
+{
+protected:
+ explicit GetFilesHelperBase(bool aRecursiveFlag)
+ : mRecursiveFlag(aRecursiveFlag)
+ {}
+
+ virtual ~GetFilesHelperBase() {}
+
+ virtual bool
+ IsCanceled()
+ {
+ return false;
+ }
+
+ nsresult
+ ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile);
+
+ nsresult
+ AddExploredDirectory(nsIFile* aDirectory);
+
+ bool
+ ShouldFollowSymLink(nsIFile* aDirectory);
+
+ bool mRecursiveFlag;
+
+ // We populate this array in the I/O thread with the BlobImpl.
+ FallibleTArray<RefPtr<BlobImpl>> mTargetBlobImplArray;
+ nsTHashtable<nsCStringHashKey> mExploredDirectories;
+};
+
+// Retrieving the list of files can be very time/IO consuming. We use this
+// helper class to do it just once.
+class GetFilesHelper : public Runnable
+ , public GetFilesHelperBase
+{
+ friend class GetFilesHelperParent;
+
+public:
+ static already_AddRefed<GetFilesHelper>
+ Create(nsIGlobalObject* aGlobal,
+ const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
+ bool aRecursiveFlag, ErrorResult& aRv);
+
+ void
+ AddPromise(Promise* aPromise);
+
+ void
+ AddCallback(GetFilesCallback* aCallback);
+
+ // CC methods
+ void Unlink();
+ void Traverse(nsCycleCollectionTraversalCallback &cb);
+
+protected:
+ GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag);
+
+ virtual ~GetFilesHelper();
+
+ void
+ SetDirectoryPath(const nsAString& aDirectoryPath)
+ {
+ mDirectoryPath = aDirectoryPath;
+ }
+
+ virtual bool
+ IsCanceled() override
+ {
+ MutexAutoLock lock(mMutex);
+ return mCanceled;
+ }
+
+ virtual void
+ Work(ErrorResult& aRv);
+
+ virtual void
+ Cancel() {};
+
+ NS_IMETHOD
+ Run() override;
+
+ void
+ RunIO();
+
+ void
+ RunMainThread();
+
+ void
+ OperationCompleted();
+
+ void
+ ResolveOrRejectPromise(Promise* aPromise);
+
+ void
+ RunCallback(GetFilesCallback* aCallback);
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ bool mListingCompleted;
+ nsString mDirectoryPath;
+
+ // This is the real File sequence that we expose via Promises.
+ Sequence<RefPtr<File>> mFiles;
+
+ // Error code to propagate.
+ nsresult mErrorResult;
+
+ nsTArray<RefPtr<Promise>> mPromises;
+ nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
+
+ Mutex mMutex;
+
+ // This variable is protected by mutex.
+ bool mCanceled;
+};
+
+class GetFilesHelperChild final : public GetFilesHelper
+{
+public:
+ GetFilesHelperChild(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
+ : GetFilesHelper(aGlobal, aRecursiveFlag)
+ , mPendingOperation(false)
+ {}
+
+ virtual void
+ Work(ErrorResult& aRv) override;
+
+ virtual void
+ Cancel() override;
+
+ bool
+ AppendBlobImpl(BlobImpl* aBlobImpl);
+
+ void
+ Finished(nsresult aResult);
+
+private:
+ nsID mUUID;
+ bool mPendingOperation;
+};
+
+class GetFilesHelperParentCallback;
+
+class GetFilesHelperParent final : public GetFilesHelper
+{
+ friend class GetFilesHelperParentCallback;
+
+public:
+ static already_AddRefed<GetFilesHelperParent>
+ Create(const nsID& aUUID, const nsAString& aDirectoryPath,
+ bool aRecursiveFlag, ContentParent* aContentParent, ErrorResult& aRv);
+
+private:
+ GetFilesHelperParent(const nsID& aUUID, ContentParent* aContentParent,
+ bool aRecursiveFlag);
+
+ ~GetFilesHelperParent();
+
+ RefPtr<ContentParent> mContentParent;
+ nsID mUUID;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_GetFilesHelper_h
diff --git a/dom/filesystem/GetFilesTask.cpp b/dom/filesystem/GetFilesTask.cpp
new file mode 100644
index 000000000..6682fb7d3
--- /dev/null
+++ b/dom/filesystem/GetFilesTask.cpp
@@ -0,0 +1,263 @@
+/* -*- 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,
+ Directory* aDirectory,
+ nsIFile* aTargetPath,
+ bool aRecursiveFlag,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aFileSystem);
+ MOZ_ASSERT(aDirectory);
+ 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, aDirectory, 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,
+ Directory* aDirectory,
+ nsIFile* aTargetPath,
+ bool aRecursiveFlag)
+ : FileSystemTaskChildBase(aFileSystem)
+ , mDirectory(aDirectory)
+ , mTargetPath(aTargetPath)
+ , mRecursiveFlag(aRecursiveFlag)
+{
+ MOZ_ASSERT(aFileSystem);
+ MOZ_ASSERT(aDirectory);
+ 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();
+ }
+
+ nsAutoString domPath;
+ mDirectory->GetPath(domPath, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return FileSystemGetFilesParams();
+ }
+
+ return FileSystemGetFilesParams(aSerializedDOMPath, path, domPath,
+ 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];
+ RefPtr<BlobImpl> blobImpl =
+ static_cast<BlobChild*>(data.blobChild())->GetBlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ mTargetData[i] = File::Create(mFileSystem->GetParentObject(), blobImpl);
+ }
+}
+
+void
+GetFilesTaskChild::HandlerCallback()
+{
+ mFileSystem->AssertIsOnOwningThread();
+ if (mFileSystem->IsShutdown()) {
+ mPromise = nullptr;
+ return;
+ }
+
+ if (HasError()) {
+ mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ mPromise = nullptr;
+ return;
+ }
+
+ mPromise->MaybeResolve(mTargetData);
+ mPromise = nullptr;
+}
+
+/**
+ * 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);
+
+ aRv = NS_NewLocalFile(aParam.realPath(), 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)
+ , GetFilesHelperBase(aParam.recursiveFlag())
+ , mDirectoryDOMPath(aParam.domPath())
+{
+ 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(mTargetBlobImplArray.Length(), mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ FileSystemFilesResponse response;
+ return response;
+ }
+
+ for (unsigned i = 0; i < mTargetBlobImplArray.Length(); i++) {
+ BlobParent* blobParent =
+ BlobParent::GetOrCreate(mRequestParent->Manager(),
+ mTargetBlobImplArray[i]);
+ inputs[i] = FileSystemFileResponse(blobParent, nullptr);
+ }
+
+ 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;
+ }
+
+ bool isDir;
+ rv = mTargetPath->IsDirectory(&isDir);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDir) {
+ return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+ }
+
+ // Get isDirectory.
+ rv = ExploreDirectory(mDirectoryDOMPath, mTargetPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GetFilesTaskParent::GetTargetPath(nsAString& aPath) const
+{
+ return mTargetPath->GetPath(aPath);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/GetFilesTask.h b/dom/filesystem/GetFilesTask.h
new file mode 100644
index 000000000..be08a5d18
--- /dev/null
+++ b/dom/filesystem/GetFilesTask.h
@@ -0,0 +1,95 @@
+/* -*- 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/dom/GetFilesHelper.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class FileSystemGetFilesParams;
+
+class GetFilesTaskChild final : public FileSystemTaskChildBase
+{
+public:
+ static already_AddRefed<GetFilesTaskChild>
+ Create(FileSystemBase* aFileSystem,
+ Directory* aDirectory,
+ nsIFile* aTargetPath,
+ bool aRecursiveFlag,
+ ErrorResult& aRv);
+
+ virtual
+ ~GetFilesTaskChild();
+
+ already_AddRefed<Promise>
+ GetPromise();
+
+private:
+ // If aDirectoryOnly is set, we should ensure that the target is a directory.
+ GetFilesTaskChild(FileSystemBase* aFileSystem,
+ Directory* aDirectory,
+ 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;
+ RefPtr<Directory> mDirectory;
+ nsCOMPtr<nsIFile> mTargetPath;
+ bool mRecursiveFlag;
+
+ // We store the fullpath and the dom path of Files.
+ FallibleTArray<RefPtr<File>> mTargetData;
+};
+
+class GetFilesTaskParent final : public FileSystemTaskParentBase
+ , public GetFilesHelperBase
+{
+public:
+ static already_AddRefed<GetFilesTaskParent>
+ Create(FileSystemBase* aFileSystem,
+ const FileSystemGetFilesParams& aParam,
+ FileSystemRequestParent* aParent,
+ ErrorResult& aRv);
+
+ nsresult
+ GetTargetPath(nsAString& aPath) const override;
+
+private:
+ GetFilesTaskParent(FileSystemBase* aFileSystem,
+ const FileSystemGetFilesParams& aParam,
+ FileSystemRequestParent* aParent);
+
+ virtual FileSystemResponseValue
+ GetSuccessRequestResult(ErrorResult& aRv) const override;
+
+ virtual nsresult
+ IOWork() override;
+
+ nsString mDirectoryDOMPath;
+ nsCOMPtr<nsIFile> mTargetPath;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GetFilesTask_h
diff --git a/dom/filesystem/OSFileSystem.cpp b/dom/filesystem/OSFileSystem.cpp
new file mode 100644
index 000000000..1e9a5ddbd
--- /dev/null
+++ b/dom/filesystem/OSFileSystem.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "mozilla/dom/OSFileSystem.h"
+
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "nsIGlobalObject.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace dom {
+
+OSFileSystem::OSFileSystem(const nsAString& aRootDir)
+{
+ mLocalRootPath = aRootDir;
+}
+
+already_AddRefed<FileSystemBase>
+OSFileSystem::Clone()
+{
+ AssertIsOnOwningThread();
+
+ RefPtr<OSFileSystem> fs = new OSFileSystem(mLocalRootPath);
+ if (mParent) {
+ fs->Init(mParent);
+ }
+
+ return fs.forget();
+}
+
+void
+OSFileSystem::Init(nsISupports* aParent)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mParent, "No duple Init() calls");
+ MOZ_ASSERT(aParent);
+
+ mParent = aParent;
+
+#ifdef DEBUG
+ nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(aParent);
+ MOZ_ASSERT(obj);
+#endif
+}
+
+nsISupports*
+OSFileSystem::GetParentObject() const
+{
+ AssertIsOnOwningThread();
+ return mParent;
+}
+
+bool
+OSFileSystem::IsSafeFile(nsIFile* aFile) const
+{
+ // The concept of "safe files" is specific to the Device Storage API where
+ // files are only "safe" if they're of a type that is appropriate for the
+ // area of device storage that is being used.
+ MOZ_CRASH("Don't use OSFileSystem with the Device Storage API");
+ return true;
+}
+
+bool
+OSFileSystem::IsSafeDirectory(Directory* aDir) const
+{
+ // The concept of "safe directory" is specific to the Device Storage API
+ // where a directory is only "safe" if it belongs to the area of device
+ // storage that it is being used with.
+ MOZ_CRASH("Don't use OSFileSystem with the Device Storage API");
+ return true;
+}
+
+void
+OSFileSystem::Unlink()
+{
+ AssertIsOnOwningThread();
+ mParent = nullptr;
+}
+
+void
+OSFileSystem::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+ AssertIsOnOwningThread();
+
+ OSFileSystem* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent);
+}
+
+void
+OSFileSystem::SerializeDOMPath(nsAString& aOutput) const
+{
+ AssertIsOnOwningThread();
+ aOutput = mLocalRootPath;
+}
+
+/**
+ * OSFileSystemParent
+ */
+
+OSFileSystemParent::OSFileSystemParent(const nsAString& aRootDir)
+{
+ mLocalRootPath = aRootDir;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/OSFileSystem.h b/dom/filesystem/OSFileSystem.h
new file mode 100644
index 000000000..165f6b99a
--- /dev/null
+++ b/dom/filesystem/OSFileSystem.h
@@ -0,0 +1,132 @@
+/* -*- 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_OSFileSystem_h
+#define mozilla_dom_OSFileSystem_h
+
+#include "mozilla/dom/FileSystemBase.h"
+
+namespace mozilla {
+namespace dom {
+
+class OSFileSystem final : public FileSystemBase
+{
+public:
+ explicit OSFileSystem(const nsAString& aRootDir);
+
+ void
+ Init(nsISupports* aParent);
+
+ // Overrides FileSystemBase
+
+ virtual already_AddRefed<FileSystemBase>
+ Clone() override;
+
+ virtual bool
+ ShouldCreateDirectory() override
+ {
+ MOZ_CRASH("This should not be called.");
+ // Because OSFileSystem should not be used when the creation of directories
+ // is needed. For that we have OSFileSystemParent.
+ return false;
+ }
+
+ virtual nsISupports*
+ GetParentObject() const override;
+
+ virtual bool
+ IsSafeFile(nsIFile* aFile) const override;
+
+ virtual bool
+ IsSafeDirectory(Directory* aDir) const override;
+
+ virtual void
+ SerializeDOMPath(nsAString& aOutput) const override;
+
+ virtual bool
+ ClonableToDifferentThreadOrProcess() const override { return true; }
+
+ // CC methods
+ virtual void Unlink() override;
+ virtual void Traverse(nsCycleCollectionTraversalCallback &cb) override;
+
+private:
+ virtual ~OSFileSystem() {}
+
+ nsCOMPtr<nsISupports> mParent;
+};
+
+class OSFileSystemParent final : public FileSystemBase
+{
+public:
+ explicit OSFileSystemParent(const nsAString& aRootDir);
+
+ // Overrides FileSystemBase
+
+ virtual already_AddRefed<FileSystemBase>
+ Clone() override
+ {
+ MOZ_CRASH("This should not be called on the PBackground thread.");
+ return nullptr;
+ }
+
+ virtual bool
+ ShouldCreateDirectory() override { return false; }
+
+ virtual nsISupports*
+ GetParentObject() const override
+ {
+ MOZ_CRASH("This should not be called on the PBackground thread.");
+ return nullptr;
+ }
+
+ virtual void
+ GetDirectoryName(nsIFile* aFile, nsAString& aRetval,
+ ErrorResult& aRv) const override
+ {
+ MOZ_CRASH("This should not be called on the PBackground thread.");
+ }
+
+ virtual bool
+ IsSafeFile(nsIFile* aFile) const override
+ {
+ return true;
+ }
+
+ virtual bool
+ IsSafeDirectory(Directory* aDir) const override
+ {
+ MOZ_CRASH("This should not be called on the PBackground thread.");
+ return true;
+ }
+
+ virtual void
+ SerializeDOMPath(nsAString& aOutput) const override
+ {
+ MOZ_CRASH("This should not be called on the PBackground thread.");
+ }
+
+ // CC methods
+ virtual void
+ Unlink() override
+ {
+ MOZ_CRASH("This should not be called on the PBackground thread.");
+ }
+
+ virtual void
+ Traverse(nsCycleCollectionTraversalCallback &cb) override
+ {
+ MOZ_CRASH("This should not be called on the PBackground thread.");
+ }
+
+private:
+ virtual ~OSFileSystemParent() {}
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_OSFileSystem_h
diff --git a/dom/filesystem/PFileSystemParams.ipdlh b/dom/filesystem/PFileSystemParams.ipdlh
new file mode 100644
index 000000000..08929b6f2
--- /dev/null
+++ b/dom/filesystem/PFileSystemParams.ipdlh
@@ -0,0 +1,50 @@
+/* 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 protocol PBlob;
+
+namespace mozilla {
+namespace dom {
+
+struct FileSystemGetDirectoryListingParams
+{
+ nsString filesystem;
+ nsString realPath;
+ nsString domPath;
+
+ // 'filters' could be an array rather than a semicolon separated string
+ // (we'd then use InfallibleTArray<nsString> internally), but that is
+ // wasteful. E10s requires us to pass the filters over as a string anyway,
+ // so avoiding using an array avoids serialization on the side passing the
+ // 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;
+ nsString domPath;
+ bool recursiveFlag;
+};
+
+struct FileSystemGetFileOrDirectoryParams
+{
+ nsString filesystem;
+ nsString realPath;
+};
+
+union FileSystemParams
+{
+ FileSystemGetDirectoryListingParams;
+ FileSystemGetFilesParams;
+ FileSystemGetFileOrDirectoryParams;
+};
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/PFileSystemRequest.ipdl b/dom/filesystem/PFileSystemRequest.ipdl
new file mode 100644
index 000000000..49803a770
--- /dev/null
+++ b/dom/filesystem/PFileSystemRequest.ipdl
@@ -0,0 +1,73 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=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 protocol PBackground;
+include protocol PBlob;
+
+namespace mozilla {
+namespace dom {
+
+struct FileSystemFileResponse
+{
+ PBlob blob;
+};
+
+struct FileSystemDirectoryResponse
+{
+ nsString realPath;
+};
+
+struct FileSystemDirectoryListingResponseFile
+{
+ PBlob blob;
+};
+
+struct FileSystemDirectoryListingResponseDirectory
+{
+ // This is the full real path for the directory that we are sending via IPC.
+ nsString directoryRealPath;
+};
+
+union FileSystemDirectoryListingResponseData
+{
+ FileSystemDirectoryListingResponseFile;
+ FileSystemDirectoryListingResponseDirectory;
+};
+
+struct FileSystemDirectoryListingResponse
+{
+ FileSystemDirectoryListingResponseData[] data;
+};
+
+struct FileSystemFilesResponse
+{
+ FileSystemFileResponse[] data;
+};
+
+struct FileSystemErrorResponse
+{
+ nsresult error;
+};
+
+union FileSystemResponseValue
+{
+ FileSystemDirectoryResponse;
+ FileSystemDirectoryListingResponse;
+ FileSystemFileResponse;
+ FileSystemFilesResponse;
+ FileSystemErrorResponse;
+};
+
+protocol PFileSystemRequest
+{
+ manager PBackground;
+
+child:
+ async __delete__(FileSystemResponseValue response);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filesystem/compat/CallbackRunnables.cpp b/dom/filesystem/compat/CallbackRunnables.cpp
new file mode 100644
index 000000000..efd14b03e
--- /dev/null
+++ b/dom/filesystem/compat/CallbackRunnables.cpp
@@ -0,0 +1,306 @@
+/* -*- 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 "CallbackRunnables.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/FileSystemDirectoryReaderBinding.h"
+#include "mozilla/dom/FileSystemFileEntry.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Unused.h"
+#include "nsIGlobalObject.h"
+#include "nsIFile.h"
+#include "nsPIDOMWindow.h"
+
+#include "../GetFileOrDirectoryTask.h"
+
+namespace mozilla {
+namespace dom {
+
+EntryCallbackRunnable::EntryCallbackRunnable(FileSystemEntryCallback* aCallback,
+ FileSystemEntry* aEntry)
+ : mCallback(aCallback)
+ , mEntry(aEntry)
+{
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(aEntry);
+}
+
+NS_IMETHODIMP
+EntryCallbackRunnable::Run()
+{
+ mCallback->HandleEvent(*mEntry);
+ return NS_OK;
+}
+
+ErrorCallbackRunnable::ErrorCallbackRunnable(nsIGlobalObject* aGlobalObject,
+ ErrorCallback* aCallback,
+ nsresult aError)
+ : mGlobal(aGlobalObject)
+ , mCallback(aCallback)
+ , mError(aError)
+{
+ MOZ_ASSERT(aGlobalObject);
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(NS_FAILED(aError));
+}
+
+NS_IMETHODIMP
+ErrorCallbackRunnable::Run()
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+ if (NS_WARN_IF(!window)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DOMException> exception = DOMException::Create(mError);
+ mCallback->HandleEvent(*exception);
+ return NS_OK;
+}
+
+EmptyEntriesCallbackRunnable::EmptyEntriesCallbackRunnable(FileSystemEntriesCallback* aCallback)
+ : mCallback(aCallback)
+{
+ MOZ_ASSERT(aCallback);
+}
+
+NS_IMETHODIMP
+EmptyEntriesCallbackRunnable::Run()
+{
+ Sequence<OwningNonNull<FileSystemEntry>> sequence;
+ mCallback->HandleEvent(sequence);
+ return NS_OK;
+}
+
+GetEntryHelper::GetEntryHelper(FileSystemDirectoryEntry* aParentEntry,
+ Directory* aDirectory,
+ nsTArray<nsString>& aParts,
+ FileSystem* aFileSystem,
+ FileSystemEntryCallback* aSuccessCallback,
+ ErrorCallback* aErrorCallback,
+ FileSystemDirectoryEntry::GetInternalType aType)
+ : mParentEntry(aParentEntry)
+ , mDirectory(aDirectory)
+ , mParts(aParts)
+ , mFileSystem(aFileSystem)
+ , mSuccessCallback(aSuccessCallback)
+ , mErrorCallback(aErrorCallback)
+ , mType(aType)
+{
+ MOZ_ASSERT(aParentEntry);
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(!aParts.IsEmpty());
+ MOZ_ASSERT(aFileSystem);
+ MOZ_ASSERT(aSuccessCallback || aErrorCallback);
+}
+
+GetEntryHelper::~GetEntryHelper()
+{}
+
+namespace {
+
+nsresult
+DOMPathToRealPath(Directory* aDirectory, const nsAString& aPath,
+ nsIFile** aFile)
+{
+ nsString relativePath;
+ relativePath = aPath;
+
+ // Trim white spaces.
+ static const char kWhitespace[] = "\b\t\r\n ";
+ relativePath.Trim(kWhitespace);
+
+ nsTArray<nsString> parts;
+ if (!FileSystemUtils::IsValidRelativeDOMPath(relativePath, parts)) {
+ return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aDirectory->GetInternalNsIFile()->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (uint32_t i = 0; i < parts.Length(); ++i) {
+ rv = file->AppendRelativePath(parts[i]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+} // anonymous
+
+void
+GetEntryHelper::Run()
+{
+ MOZ_ASSERT(!mParts.IsEmpty());
+
+ nsCOMPtr<nsIFile> realPath;
+ nsresult error = DOMPathToRealPath(mDirectory, mParts[0],
+ getter_AddRefs(realPath));
+
+ ErrorResult rv;
+ RefPtr<FileSystemBase> fs = mDirectory->GetFileSystem(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ Error(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<GetFileOrDirectoryTaskChild> task =
+ GetFileOrDirectoryTaskChild::Create(fs, realPath, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ Error(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ task->SetError(error);
+ task->Start();
+
+ RefPtr<Promise> promise = task->GetPromise();
+
+ mParts.RemoveElementAt(0);
+ promise->AppendNativeHandler(this);
+}
+
+void
+GetEntryHelper::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ if(NS_WARN_IF(!aValue.isObject())) {
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ // This is not the last part of the path.
+ if (!mParts.IsEmpty()) {
+ ContinueRunning(obj);
+ return;
+ }
+
+ CompleteOperation(obj);
+}
+
+void
+GetEntryHelper::CompleteOperation(JSObject* aObj)
+{
+ MOZ_ASSERT(mParts.IsEmpty());
+
+ if (mType == FileSystemDirectoryEntry::eGetFile) {
+ RefPtr<File> file;
+ if (NS_FAILED(UNWRAP_OBJECT(File, aObj, file))) {
+ Error(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+ return;
+ }
+
+ RefPtr<FileSystemFileEntry> entry =
+ new FileSystemFileEntry(mParentEntry->GetParentObject(), file,
+ mParentEntry, mFileSystem);
+ mSuccessCallback->HandleEvent(*entry);
+ return;
+ }
+
+ MOZ_ASSERT(mType == FileSystemDirectoryEntry::eGetDirectory);
+
+ RefPtr<Directory> directory;
+ if (NS_FAILED(UNWRAP_OBJECT(Directory, aObj, directory))) {
+ Error(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+ return;
+ }
+
+ RefPtr<FileSystemDirectoryEntry> entry =
+ new FileSystemDirectoryEntry(mParentEntry->GetParentObject(), directory,
+ mParentEntry, mFileSystem);
+ mSuccessCallback->HandleEvent(*entry);
+}
+
+void
+GetEntryHelper::ContinueRunning(JSObject* aObj)
+{
+ MOZ_ASSERT(!mParts.IsEmpty());
+
+ RefPtr<Directory> directory;
+ if (NS_FAILED(UNWRAP_OBJECT(Directory, aObj, directory))) {
+ Error(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+ return;
+ }
+
+ RefPtr<FileSystemDirectoryEntry> entry =
+ new FileSystemDirectoryEntry(mParentEntry->GetParentObject(), directory,
+ mParentEntry, mFileSystem);
+
+ // Update the internal values.
+ mParentEntry = entry;
+ mDirectory = directory;
+
+ Run();
+}
+
+void
+GetEntryHelper::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ Error(NS_ERROR_DOM_NOT_FOUND_ERR);
+}
+
+void
+GetEntryHelper::Error(nsresult aError)
+{
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ if (mErrorCallback) {
+ RefPtr<ErrorCallbackRunnable> runnable =
+ new ErrorCallbackRunnable(mParentEntry->GetParentObject(),
+ mErrorCallback, aError);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ }
+}
+
+NS_IMPL_ISUPPORTS0(GetEntryHelper);
+
+/* static */ void
+FileSystemEntryCallbackHelper::Call(const Optional<OwningNonNull<FileSystemEntryCallback>>& aEntryCallback,
+ FileSystemEntry* aEntry)
+{
+ MOZ_ASSERT(aEntry);
+
+ if (aEntryCallback.WasPassed()) {
+ RefPtr<EntryCallbackRunnable> runnable =
+ new EntryCallbackRunnable(&aEntryCallback.Value(), aEntry);
+
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ }
+}
+
+/* static */ void
+ErrorCallbackHelper::Call(nsIGlobalObject* aGlobal,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ nsresult aError)
+{
+ MOZ_ASSERT(aGlobal);
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ if (aErrorCallback.WasPassed()) {
+ RefPtr<ErrorCallbackRunnable> runnable =
+ new ErrorCallbackRunnable(aGlobal, &aErrorCallback.Value(), aError);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ }
+}
+
+} // dom namespace
+} // mozilla namespace
+
diff --git a/dom/filesystem/compat/CallbackRunnables.h b/dom/filesystem/compat/CallbackRunnables.h
new file mode 100644
index 000000000..3ff77f1a9
--- /dev/null
+++ b/dom/filesystem/compat/CallbackRunnables.h
@@ -0,0 +1,128 @@
+/* -*- 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_ErrorCallbackRunnable_h
+#define mozilla_dom_ErrorCallbackRunnable_h
+
+#include "FileSystemDirectoryEntry.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemEntriesCallback;
+
+class EntryCallbackRunnable final : public Runnable
+{
+public:
+ EntryCallbackRunnable(FileSystemEntryCallback* aCallback,
+ FileSystemEntry* aEntry);
+
+ NS_IMETHOD
+ Run() override;
+
+private:
+ RefPtr<FileSystemEntryCallback> mCallback;
+ RefPtr<FileSystemEntry> mEntry;
+};
+
+class ErrorCallbackRunnable final : public Runnable
+{
+public:
+ ErrorCallbackRunnable(nsIGlobalObject* aGlobalObject,
+ ErrorCallback* aCallback,
+ nsresult aError);
+
+ NS_IMETHOD
+ Run() override;
+
+private:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<ErrorCallback> mCallback;
+ nsresult mError;
+};
+
+class EmptyEntriesCallbackRunnable final : public Runnable
+{
+public:
+ explicit EmptyEntriesCallbackRunnable(FileSystemEntriesCallback* aCallback);
+
+ NS_IMETHOD
+ Run() override;
+
+private:
+ RefPtr<FileSystemEntriesCallback> mCallback;
+};
+
+class GetEntryHelper final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ GetEntryHelper(FileSystemDirectoryEntry* aParentEntry,
+ Directory* aDirectory,
+ nsTArray<nsString>& aParts,
+ FileSystem* aFileSystem,
+ FileSystemEntryCallback* aSuccessCallback,
+ ErrorCallback* aErrorCallback,
+ FileSystemDirectoryEntry::GetInternalType aType);
+
+ void
+ Run();
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+private:
+ ~GetEntryHelper();
+
+ void
+ Error(nsresult aError);
+
+ void
+ ContinueRunning(JSObject* aObj);
+
+ void
+ CompleteOperation(JSObject* aObj);
+
+ RefPtr<FileSystemDirectoryEntry> mParentEntry;
+ RefPtr<Directory> mDirectory;
+ nsTArray<nsString> mParts;
+ RefPtr<FileSystem> mFileSystem;
+
+ RefPtr<FileSystemEntryCallback> mSuccessCallback;
+ RefPtr<ErrorCallback> mErrorCallback;
+
+ FileSystemDirectoryEntry::GetInternalType mType;
+};
+
+class FileSystemEntryCallbackHelper
+{
+public:
+ static void
+ Call(const Optional<OwningNonNull<FileSystemEntryCallback>>& aEntryCallback,
+ FileSystemEntry* aEntry);
+};
+
+class ErrorCallbackHelper
+{
+public:
+ static void
+ Call(nsIGlobalObject* aGlobal,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ nsresult aError);
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_CallbackRunnables_h
diff --git a/dom/filesystem/compat/FileSystem.cpp b/dom/filesystem/compat/FileSystem.cpp
new file mode 100644
index 000000000..b45980fc6
--- /dev/null
+++ b/dom/filesystem/compat/FileSystem.cpp
@@ -0,0 +1,75 @@
+/* -*- 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 "FileSystem.h"
+#include "FileSystemRootDirectoryEntry.h"
+#include "mozilla/dom/FileSystemBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystem, mParent, mRoot)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystem)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystem)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystem)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* static */ already_AddRefed<FileSystem>
+FileSystem::Create(nsIGlobalObject* aGlobalObject)
+
+{
+ MOZ_ASSERT(aGlobalObject);
+
+
+ nsID id;
+ nsresult rv = nsContentUtils::GenerateUUIDInPlace(id);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ char chars[NSID_LENGTH];
+ id.ToProvidedString(chars);
+
+ // Any fileSystem has an unique ID. We use UUID, but our generator produces
+ // UUID in this format '{' + UUID + '}'. We remove them with these +1 and -2.
+ nsAutoCString name(Substring(chars + 1, chars + NSID_LENGTH - 2));
+
+ RefPtr<FileSystem> fs =
+ new FileSystem(aGlobalObject, NS_ConvertUTF8toUTF16(name));
+
+ return fs.forget();
+}
+
+FileSystem::FileSystem(nsIGlobalObject* aGlobal, const nsAString& aName)
+ : mParent(aGlobal)
+ , mName(aName)
+{
+ MOZ_ASSERT(aGlobal);
+}
+
+FileSystem::~FileSystem()
+{}
+
+JSObject*
+FileSystem::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FileSystemBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FileSystem::CreateRoot(const Sequence<RefPtr<FileSystemEntry>>& aEntries)
+{
+ MOZ_ASSERT(!mRoot);
+ mRoot = new FileSystemRootDirectoryEntry(mParent, aEntries, this);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/compat/FileSystem.h b/dom/filesystem/compat/FileSystem.h
new file mode 100644
index 000000000..43ce2ea38
--- /dev/null
+++ b/dom/filesystem/compat/FileSystem.h
@@ -0,0 +1,73 @@
+/* -*- 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_FileSystem_h
+#define mozilla_dom_FileSystem_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemDirectoryEntry;
+class FileSystemEntry;
+class OwningFileOrDirectory;
+
+class FileSystem final
+ : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystem)
+
+ static already_AddRefed<FileSystem>
+ Create(nsIGlobalObject* aGlobalObject);
+
+ nsIGlobalObject*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void
+ GetName(nsAString& aName) const
+ {
+ aName = mName;
+ }
+
+ FileSystemDirectoryEntry*
+ Root() const
+ {
+ return mRoot;
+ }
+
+ void
+ CreateRoot(const Sequence<RefPtr<FileSystemEntry>>& aEntries);
+
+private:
+ explicit FileSystem(nsIGlobalObject* aGlobalObject,
+ const nsAString& aName);
+ ~FileSystem();
+
+ nsCOMPtr<nsIGlobalObject> mParent;
+ RefPtr<FileSystemDirectoryEntry> mRoot;
+ nsString mName;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystem_h
diff --git a/dom/filesystem/compat/FileSystemDirectoryEntry.cpp b/dom/filesystem/compat/FileSystemDirectoryEntry.cpp
new file mode 100644
index 000000000..3157ef654
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemDirectoryEntry.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "FileSystemDirectoryEntry.h"
+#include "CallbackRunnables.h"
+#include "FileSystemDirectoryReader.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemDirectoryEntryBinding.h"
+#include "mozilla/dom/FileSystemUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemDirectoryEntry, FileSystemEntry,
+ mDirectory)
+
+NS_IMPL_ADDREF_INHERITED(FileSystemDirectoryEntry, FileSystemEntry)
+NS_IMPL_RELEASE_INHERITED(FileSystemDirectoryEntry, FileSystemEntry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FileSystemDirectoryEntry)
+NS_INTERFACE_MAP_END_INHERITING(FileSystemEntry)
+
+FileSystemDirectoryEntry::FileSystemDirectoryEntry(nsIGlobalObject* aGlobal,
+ Directory* aDirectory,
+ FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem)
+ : FileSystemEntry(aGlobal, aParentEntry, aFileSystem)
+ , mDirectory(aDirectory)
+{
+ MOZ_ASSERT(aGlobal);
+}
+
+FileSystemDirectoryEntry::~FileSystemDirectoryEntry()
+{}
+
+JSObject*
+FileSystemDirectoryEntry::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return FileSystemDirectoryEntryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FileSystemDirectoryEntry::GetName(nsAString& aName, ErrorResult& aRv) const
+{
+ MOZ_ASSERT(mDirectory);
+ mDirectory->GetName(aName, aRv);
+}
+
+void
+FileSystemDirectoryEntry::GetFullPath(nsAString& aPath, ErrorResult& aRv) const
+{
+ MOZ_ASSERT(mDirectory);
+ mDirectory->GetPath(aPath, aRv);
+}
+
+already_AddRefed<FileSystemDirectoryReader>
+FileSystemDirectoryEntry::CreateReader()
+{
+ MOZ_ASSERT(mDirectory);
+
+ RefPtr<FileSystemDirectoryReader> reader =
+ new FileSystemDirectoryReader(this, Filesystem(), mDirectory);
+ return reader.forget();
+}
+
+void
+FileSystemDirectoryEntry::GetInternal(const nsAString& aPath,
+ const FileSystemFlags& aFlag,
+ const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ GetInternalType aType)
+{
+ MOZ_ASSERT(mDirectory);
+
+ if (!aSuccessCallback.WasPassed() && !aErrorCallback.WasPassed()) {
+ return;
+ }
+
+ if (aFlag.mCreate) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsTArray<nsString> parts;
+ if (!FileSystemUtils::IsValidRelativeDOMPath(aPath, parts)) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+
+ RefPtr<GetEntryHelper> helper =
+ new GetEntryHelper(this, mDirectory, parts, Filesystem(),
+ aSuccessCallback.WasPassed()
+ ? &aSuccessCallback.Value() : nullptr,
+ aErrorCallback.WasPassed()
+ ? &aErrorCallback.Value() : nullptr,
+ aType);
+ helper->Run();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/compat/FileSystemDirectoryEntry.h b/dom/filesystem/compat/FileSystemDirectoryEntry.h
new file mode 100644
index 000000000..96dc21831
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemDirectoryEntry.h
@@ -0,0 +1,84 @@
+/* -*- 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_FileSystemDirectoryEntry_h
+#define mozilla_dom_FileSystemDirectoryEntry_h
+
+#include "mozilla/dom/FileSystemEntry.h"
+
+namespace mozilla {
+namespace dom {
+
+class Directory;
+class FileSystemDirectoryReader;
+
+class FileSystemDirectoryEntry : public FileSystemEntry
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemDirectoryEntry,
+ FileSystemEntry)
+
+ FileSystemDirectoryEntry(nsIGlobalObject* aGlobalObject,
+ Directory* aDirectory,
+ FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem);
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual bool
+ IsDirectory() const override
+ {
+ return true;
+ }
+
+ virtual void
+ GetName(nsAString& aName, ErrorResult& aRv) const override;
+
+ virtual void
+ GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const override;
+
+ virtual already_AddRefed<FileSystemDirectoryReader>
+ CreateReader();
+
+ void
+ GetFile(const Optional<nsAString>& aPath, const FileSystemFlags& aFlag,
+ const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback)
+ {
+ GetInternal(aPath.WasPassed() ? aPath.Value() : EmptyString(),
+ aFlag, aSuccessCallback, aErrorCallback, eGetFile);
+ }
+
+ void
+ GetDirectory(const Optional<nsAString>& aPath, const FileSystemFlags& aFlag,
+ const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback)
+ {
+ GetInternal(aPath.WasPassed() ? aPath.Value() : EmptyString(),
+ aFlag, aSuccessCallback, aErrorCallback, eGetDirectory);
+ }
+
+ enum GetInternalType { eGetFile, eGetDirectory };
+
+ virtual void
+ GetInternal(const nsAString& aPath, const FileSystemFlags& aFlag,
+ const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ GetInternalType aType);
+
+protected:
+ virtual ~FileSystemDirectoryEntry();
+
+private:
+ RefPtr<Directory> mDirectory;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemDirectoryEntry_h
diff --git a/dom/filesystem/compat/FileSystemDirectoryReader.cpp b/dom/filesystem/compat/FileSystemDirectoryReader.cpp
new file mode 100644
index 000000000..137437378
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemDirectoryReader.cpp
@@ -0,0 +1,188 @@
+/* -*- 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 "FileSystemDirectoryReader.h"
+#include "CallbackRunnables.h"
+#include "FileSystemFileEntry.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"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class PromiseHandler final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ PromiseHandler(FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem,
+ FileSystemEntriesCallback* aSuccessCallback,
+ ErrorCallback* aErrorCallback)
+ : mParentEntry(aParentEntry)
+ , mFileSystem(aFileSystem)
+ , mSuccessCallback(aSuccessCallback)
+ , mErrorCallback(aErrorCallback)
+ {
+ MOZ_ASSERT(aParentEntry);
+ MOZ_ASSERT(aFileSystem);
+ 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<FileSystemEntry>> 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<FileSystemFileEntry> entry =
+ new FileSystemFileEntry(mParentEntry->GetParentObject(), file,
+ mParentEntry, mFileSystem);
+ sequence[i] = entry;
+ continue;
+ }
+
+ RefPtr<Directory> directory;
+ if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Directory, valueObj,
+ directory)))) {
+ return;
+ }
+
+ RefPtr<FileSystemDirectoryEntry> entry =
+ new FileSystemDirectoryEntry(mParentEntry->GetParentObject(), directory,
+ mParentEntry, mFileSystem);
+ sequence[i] = entry;
+ }
+
+ mSuccessCallback->HandleEvent(sequence);
+ }
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ if (mErrorCallback) {
+ RefPtr<ErrorCallbackRunnable> runnable =
+ new ErrorCallbackRunnable(mParentEntry->GetParentObject(),
+ mErrorCallback,
+ NS_ERROR_DOM_INVALID_STATE_ERR);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ }
+ }
+
+private:
+ ~PromiseHandler() {}
+
+ RefPtr<FileSystemDirectoryEntry> mParentEntry;
+ RefPtr<FileSystem> mFileSystem;
+ RefPtr<FileSystemEntriesCallback> mSuccessCallback;
+ RefPtr<ErrorCallback> mErrorCallback;
+};
+
+NS_IMPL_ISUPPORTS0(PromiseHandler);
+
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemDirectoryReader, mParentEntry,
+ mDirectory, mFileSystem)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemDirectoryReader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemDirectoryReader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemDirectoryReader)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+FileSystemDirectoryReader::FileSystemDirectoryReader(FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem,
+ Directory* aDirectory)
+ : mParentEntry(aParentEntry)
+ , mFileSystem(aFileSystem)
+ , mDirectory(aDirectory)
+ , mAlreadyRead(false)
+{
+ MOZ_ASSERT(aParentEntry);
+ MOZ_ASSERT(aFileSystem);
+}
+
+FileSystemDirectoryReader::~FileSystemDirectoryReader()
+{}
+
+JSObject*
+FileSystemDirectoryReader::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return FileSystemDirectoryReaderBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FileSystemDirectoryReader::ReadEntries(FileSystemEntriesCallback& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(mDirectory);
+
+ if (mAlreadyRead) {
+ RefPtr<EmptyEntriesCallbackRunnable> runnable =
+ new EmptyEntriesCallbackRunnable(&aSuccessCallback);
+ aRv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "NS_DispatchToMainThread 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())) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ rv.StealNSResult());
+ return;
+ }
+
+ RefPtr<PromiseHandler> handler =
+ new PromiseHandler(mParentEntry, mFileSystem, &aSuccessCallback,
+ aErrorCallback.WasPassed()
+ ? &aErrorCallback.Value() : nullptr);
+ promise->AppendNativeHandler(handler);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/compat/FileSystemDirectoryReader.h b/dom/filesystem/compat/FileSystemDirectoryReader.h
new file mode 100644
index 000000000..391a79329
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemDirectoryReader.h
@@ -0,0 +1,64 @@
+/* -*- 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_FileSystemDirectoryReader_h
+#define mozilla_dom_FileSystemDirectoryReader_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/FileSystemDirectoryEntry.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class Directory;
+class FileSystem;
+class FileSystemEntriesCallback;
+
+class FileSystemDirectoryReader
+ : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystemDirectoryReader)
+
+ explicit FileSystemDirectoryReader(FileSystemDirectoryEntry* aDirectoryEntry,
+ FileSystem* aFileSystem,
+ Directory* aDirectory);
+
+ nsIGlobalObject*
+ GetParentObject() const
+ {
+ return mParentEntry->GetParentObject();
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual void
+ ReadEntries(FileSystemEntriesCallback& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ ErrorResult& aRv);
+
+protected:
+ virtual ~FileSystemDirectoryReader();
+
+private:
+ RefPtr<FileSystemDirectoryEntry> mParentEntry;
+ RefPtr<FileSystem> mFileSystem;
+ RefPtr<Directory> mDirectory;
+
+ bool mAlreadyRead;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemDirectoryReader_h
diff --git a/dom/filesystem/compat/FileSystemEntry.cpp b/dom/filesystem/compat/FileSystemEntry.cpp
new file mode 100644
index 000000000..638c2c6db
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemEntry.cpp
@@ -0,0 +1,89 @@
+/* -*- 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 "FileSystemEntry.h"
+#include "FileSystemDirectoryEntry.h"
+#include "FileSystemFileEntry.h"
+#include "mozilla/dom/FileSystemEntryBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemEntry, mParent, mParentEntry,
+ mFileSystem)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemEntry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemEntry)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* static */ already_AddRefed<FileSystemEntry>
+FileSystemEntry::Create(nsIGlobalObject* aGlobalObject,
+ const OwningFileOrDirectory& aFileOrDirectory,
+ FileSystem* aFileSystem)
+{
+ MOZ_ASSERT(aGlobalObject);
+ MOZ_ASSERT(aFileSystem);
+
+ RefPtr<FileSystemEntry> entry;
+ if (aFileOrDirectory.IsFile()) {
+ entry = new FileSystemFileEntry(aGlobalObject,
+ aFileOrDirectory.GetAsFile(),
+ nullptr,
+ aFileSystem);
+ } else {
+ MOZ_ASSERT(aFileOrDirectory.IsDirectory());
+ entry = new FileSystemDirectoryEntry(aGlobalObject,
+ aFileOrDirectory.GetAsDirectory(),
+ nullptr,
+ aFileSystem);
+ }
+
+ return entry.forget();
+}
+
+FileSystemEntry::FileSystemEntry(nsIGlobalObject* aGlobal,
+ FileSystemEntry* aParentEntry,
+ FileSystem* aFileSystem)
+ : mParent(aGlobal)
+ , mParentEntry(aParentEntry)
+ , mFileSystem(aFileSystem)
+{
+ MOZ_ASSERT(aGlobal);
+ MOZ_ASSERT(aFileSystem);
+}
+
+FileSystemEntry::~FileSystemEntry()
+{}
+
+JSObject*
+FileSystemEntry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FileSystemEntryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FileSystemEntry::GetParent(const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback)
+{
+ if (!aSuccessCallback.WasPassed() && !aErrorCallback.WasPassed()) {
+ return;
+ }
+
+ if (mParentEntry) {
+ FileSystemEntryCallbackHelper::Call(aSuccessCallback, mParentEntry);
+ return;
+ }
+
+ FileSystemEntryCallbackHelper::Call(aSuccessCallback, this);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/compat/FileSystemEntry.h b/dom/filesystem/compat/FileSystemEntry.h
new file mode 100644
index 000000000..769fb8f3f
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemEntry.h
@@ -0,0 +1,89 @@
+/* -*- 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_FileSystemEntry_h
+#define mozilla_dom_FileSystemEntry_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/FileSystemBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class FileSystem;
+class OwningFileOrDirectory;
+
+class FileSystemEntry
+ : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystemEntry)
+
+ static already_AddRefed<FileSystemEntry>
+ Create(nsIGlobalObject* aGlobalObject,
+ const OwningFileOrDirectory& aFileOrDirectory,
+ FileSystem* aFileSystem);
+
+ nsIGlobalObject*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual bool
+ IsFile() const
+ {
+ return false;
+ }
+
+ virtual bool
+ IsDirectory() const
+ {
+ return false;
+ }
+
+ virtual void
+ GetName(nsAString& aName, ErrorResult& aRv) const = 0;
+
+ virtual void
+ GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const = 0;
+
+ void
+ GetParent(const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback);
+
+ FileSystem*
+ Filesystem() const
+ {
+ return mFileSystem;
+ }
+
+protected:
+ FileSystemEntry(nsIGlobalObject* aGlobalObject,
+ FileSystemEntry* aParentEntry,
+ FileSystem* aFileSystem);
+ virtual ~FileSystemEntry();
+
+private:
+ nsCOMPtr<nsIGlobalObject> mParent;
+ RefPtr<FileSystemEntry> mParentEntry;
+ RefPtr<FileSystem> mFileSystem;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemEntry_h
diff --git a/dom/filesystem/compat/FileSystemFileEntry.cpp b/dom/filesystem/compat/FileSystemFileEntry.cpp
new file mode 100644
index 000000000..6302df06d
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemFileEntry.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "FileSystemFileEntry.h"
+#include "CallbackRunnables.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MultipartBlobImpl.h"
+#include "mozilla/dom/FileSystemFileEntryBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class FileCallbackRunnable final : public Runnable
+{
+public:
+ FileCallbackRunnable(FileCallback* aCallback, ErrorCallback* aErrorCallback,
+ File* aFile)
+ : mCallback(aCallback)
+ , mErrorCallback(aErrorCallback)
+ , mFile(aFile)
+ {
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(aFile);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ // Here we clone the File object.
+
+ nsAutoString name;
+ mFile->GetName(name);
+
+ nsAutoString type;
+ mFile->GetType(type);
+
+ nsTArray<RefPtr<BlobImpl>> blobImpls;
+ blobImpls.AppendElement(mFile->Impl());
+
+ ErrorResult rv;
+ RefPtr<BlobImpl> blobImpl =
+ MultipartBlobImpl::Create(Move(blobImpls), name, type, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ if (mErrorCallback) {
+ RefPtr<DOMException> exception =
+ DOMException::Create(rv.StealNSResult());
+ mErrorCallback->HandleEvent(*exception);
+ }
+
+ return NS_OK;
+ }
+
+ RefPtr<File> file = File::Create(mFile->GetParentObject(), blobImpl);
+ MOZ_ASSERT(file);
+
+ mCallback->HandleEvent(*file);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<FileCallback> mCallback;
+ RefPtr<ErrorCallback> mErrorCallback;
+ RefPtr<File> mFile;
+};
+
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemFileEntry, FileSystemEntry, mFile)
+
+NS_IMPL_ADDREF_INHERITED(FileSystemFileEntry, FileSystemEntry)
+NS_IMPL_RELEASE_INHERITED(FileSystemFileEntry, FileSystemEntry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FileSystemFileEntry)
+NS_INTERFACE_MAP_END_INHERITING(FileSystemEntry)
+
+FileSystemFileEntry::FileSystemFileEntry(nsIGlobalObject* aGlobal,
+ File* aFile,
+ FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem)
+ : FileSystemEntry(aGlobal, aParentEntry, aFileSystem)
+ , mFile(aFile)
+{
+ MOZ_ASSERT(aGlobal);
+ MOZ_ASSERT(mFile);
+}
+
+FileSystemFileEntry::~FileSystemFileEntry()
+{}
+
+JSObject*
+FileSystemFileEntry::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return FileSystemFileEntryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FileSystemFileEntry::GetName(nsAString& aName, ErrorResult& aRv) const
+{
+ mFile->GetName(aName);
+}
+
+void
+FileSystemFileEntry::GetFullPath(nsAString& aPath, ErrorResult& aRv) const
+{
+ mFile->Impl()->GetDOMPath(aPath);
+ if (aPath.IsEmpty()) {
+ // We're under the root directory. webkitRelativePath
+ // (implemented as GetPath) is for cases when file is selected because its
+ // ancestor directory is selected. But that is not the case here, so need to
+ // manually prepend '/'.
+ nsAutoString name;
+ mFile->GetName(name);
+ aPath.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ aPath.Append(name);
+ }
+}
+
+void
+FileSystemFileEntry::GetFile(FileCallback& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const
+{
+ RefPtr<FileCallbackRunnable> runnable =
+ new FileCallbackRunnable(&aSuccessCallback,
+ aErrorCallback.WasPassed()
+ ? &aErrorCallback.Value() : nullptr,
+ mFile);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/compat/FileSystemFileEntry.h b/dom/filesystem/compat/FileSystemFileEntry.h
new file mode 100644
index 000000000..06b76e9f8
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemFileEntry.h
@@ -0,0 +1,57 @@
+/* -*- 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_FileSystemFileEntry_h
+#define mozilla_dom_FileSystemFileEntry_h
+
+#include "mozilla/dom/FileSystemEntry.h"
+
+namespace mozilla {
+namespace dom {
+
+class File;
+class FileCallback;
+class FileSystemDirectoryEntry;
+
+class FileSystemFileEntry final : public FileSystemEntry
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemFileEntry, FileSystemEntry)
+
+ FileSystemFileEntry(nsIGlobalObject* aGlobalObject, File* aFile,
+ FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem);
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual bool
+ IsFile() const override
+ {
+ return true;
+ }
+
+ virtual void
+ GetName(nsAString& aName, ErrorResult& aRv) const override;
+
+ virtual void
+ GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const override;
+
+ void
+ GetFile(FileCallback& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const;
+
+private:
+ ~FileSystemFileEntry();
+
+ RefPtr<File> mFile;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemFileEntry_h
diff --git a/dom/filesystem/compat/FileSystemRootDirectoryEntry.cpp b/dom/filesystem/compat/FileSystemRootDirectoryEntry.cpp
new file mode 100644
index 000000000..68ce62aa2
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemRootDirectoryEntry.cpp
@@ -0,0 +1,146 @@
+/* -*- 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 "FileSystemRootDirectoryEntry.h"
+#include "FileSystemRootDirectoryReader.h"
+#include "mozilla/dom/FileSystemUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemRootDirectoryEntry,
+ FileSystemDirectoryEntry, mEntries)
+
+NS_IMPL_ADDREF_INHERITED(FileSystemRootDirectoryEntry, FileSystemDirectoryEntry)
+NS_IMPL_RELEASE_INHERITED(FileSystemRootDirectoryEntry, FileSystemDirectoryEntry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FileSystemRootDirectoryEntry)
+NS_INTERFACE_MAP_END_INHERITING(FileSystemDirectoryEntry)
+
+FileSystemRootDirectoryEntry::FileSystemRootDirectoryEntry(nsIGlobalObject* aGlobal,
+ const Sequence<RefPtr<FileSystemEntry>>& aEntries,
+ FileSystem* aFileSystem)
+ : FileSystemDirectoryEntry(aGlobal, nullptr, nullptr, aFileSystem)
+ , mEntries(aEntries)
+{
+ MOZ_ASSERT(aGlobal);
+}
+
+FileSystemRootDirectoryEntry::~FileSystemRootDirectoryEntry()
+{}
+
+void
+FileSystemRootDirectoryEntry::GetName(nsAString& aName, ErrorResult& aRv) const
+{
+ aName.Truncate();
+}
+
+void
+FileSystemRootDirectoryEntry::GetFullPath(nsAString& aPath, ErrorResult& aRv) const
+{
+ aPath.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+}
+
+already_AddRefed<FileSystemDirectoryReader>
+FileSystemRootDirectoryEntry::CreateReader()
+{
+ RefPtr<FileSystemDirectoryReader> reader =
+ new FileSystemRootDirectoryReader(this, Filesystem(), mEntries);
+ return reader.forget();
+}
+
+void
+FileSystemRootDirectoryEntry::GetInternal(const nsAString& aPath,
+ const FileSystemFlags& aFlag,
+ const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ GetInternalType aType)
+{
+ if (!aSuccessCallback.WasPassed() && !aErrorCallback.WasPassed()) {
+ return;
+ }
+
+ if (aFlag.mCreate) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsTArray<nsString> parts;
+ if (!FileSystemUtils::IsValidRelativeDOMPath(aPath, parts)) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+
+ MOZ_ASSERT(!parts.IsEmpty());
+
+ RefPtr<FileSystemEntry> entry;
+ for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+ ErrorResult rv;
+ nsAutoString name;
+ mEntries[i]->GetName(name, rv);
+
+ if (NS_WARN_IF(rv.Failed())) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ rv.StealNSResult());
+ return;
+ }
+
+ if (name == parts[0]) {
+ entry = mEntries[i];
+ break;
+ }
+ }
+
+ // Not found.
+ if (!entry) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+
+ // No subdirectory in the path.
+ if (parts.Length() == 1) {
+ if ((entry->IsFile() && aType == eGetDirectory) ||
+ (entry->IsDirectory() && aType == eGetFile)) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+ return;
+ }
+
+ if (aSuccessCallback.WasPassed()) {
+ RefPtr<EntryCallbackRunnable> runnable =
+ new EntryCallbackRunnable(&aSuccessCallback.Value(), entry);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ }
+ return;
+ }
+
+ // Subdirectories, but this is a file.
+ if (entry->IsFile()) {
+ ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+ NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+
+ // Let's recreate a path without the first directory.
+ nsAutoString path;
+ for (uint32_t i = 1, len = parts.Length(); i < len; ++i) {
+ path.Append(parts[i]);
+ if (i < len - 1) {
+ path.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ }
+ }
+
+ auto* directoryEntry = static_cast<FileSystemDirectoryEntry*>(entry.get());
+ directoryEntry->GetInternal(path, aFlag, aSuccessCallback, aErrorCallback,
+ aType);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/compat/FileSystemRootDirectoryEntry.h b/dom/filesystem/compat/FileSystemRootDirectoryEntry.h
new file mode 100644
index 000000000..28c151ea2
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemRootDirectoryEntry.h
@@ -0,0 +1,53 @@
+/* -*- 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_FileSystemRootDirectoryEntry_h
+#define mozilla_dom_FileSystemRootDirectoryEntry_h
+
+#include "mozilla/dom/FileSystemDirectoryEntry.h"
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemRootDirectoryEntry final : public FileSystemDirectoryEntry
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemRootDirectoryEntry, FileSystemDirectoryEntry)
+
+ FileSystemRootDirectoryEntry(nsIGlobalObject* aGlobalObject,
+ const Sequence<RefPtr<FileSystemEntry>>& aEntries,
+ FileSystem* aFileSystem);
+
+ virtual void
+ GetName(nsAString& aName, ErrorResult& aRv) const override;
+
+ virtual void
+ GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const override;
+
+ virtual already_AddRefed<FileSystemDirectoryReader>
+ CreateReader() override;
+
+private:
+ ~FileSystemRootDirectoryEntry();
+
+ virtual void
+ GetInternal(const nsAString& aPath, const FileSystemFlags& aFlag,
+ const Optional<OwningNonNull<FileSystemEntryCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ GetInternalType aType) override;
+
+ void
+ Error(const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ nsresult aError) const;
+
+ Sequence<RefPtr<FileSystemEntry>> mEntries;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemRootDirectoryEntry_h
diff --git a/dom/filesystem/compat/FileSystemRootDirectoryReader.cpp b/dom/filesystem/compat/FileSystemRootDirectoryReader.cpp
new file mode 100644
index 000000000..5b4a41752
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemRootDirectoryReader.cpp
@@ -0,0 +1,96 @@
+/* -*- 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 "FileSystemRootDirectoryReader.h"
+#include "CallbackRunnables.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class EntriesCallbackRunnable final : public Runnable
+{
+public:
+ EntriesCallbackRunnable(FileSystemEntriesCallback* aCallback,
+ const Sequence<RefPtr<FileSystemEntry>>& aEntries)
+ : mCallback(aCallback)
+ , mEntries(aEntries)
+ {
+ MOZ_ASSERT(aCallback);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ Sequence<OwningNonNull<FileSystemEntry>> entries;
+ for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+ if (!entries.AppendElement(mEntries[i].forget(), fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mCallback->HandleEvent(entries);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<FileSystemEntriesCallback> mCallback;
+ Sequence<RefPtr<FileSystemEntry>> mEntries;
+};
+
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemRootDirectoryReader,
+ FileSystemDirectoryReader, mEntries)
+
+NS_IMPL_ADDREF_INHERITED(FileSystemRootDirectoryReader,
+ FileSystemDirectoryReader)
+NS_IMPL_RELEASE_INHERITED(FileSystemRootDirectoryReader,
+ FileSystemDirectoryReader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FileSystemRootDirectoryReader)
+NS_INTERFACE_MAP_END_INHERITING(FileSystemDirectoryReader)
+
+FileSystemRootDirectoryReader::FileSystemRootDirectoryReader(FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem,
+ const Sequence<RefPtr<FileSystemEntry>>& aEntries)
+ : FileSystemDirectoryReader(aParentEntry, aFileSystem, nullptr)
+ , mEntries(aEntries)
+ , mAlreadyRead(false)
+{
+ MOZ_ASSERT(aParentEntry);
+ MOZ_ASSERT(aFileSystem);
+}
+
+FileSystemRootDirectoryReader::~FileSystemRootDirectoryReader()
+{}
+
+void
+FileSystemRootDirectoryReader::ReadEntries(FileSystemEntriesCallback& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ ErrorResult& aRv)
+{
+ if (mAlreadyRead) {
+ RefPtr<EmptyEntriesCallbackRunnable> runnable =
+ new EmptyEntriesCallbackRunnable(&aSuccessCallback);
+ aRv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "NS_DispatchToMainThread failed");
+ return;
+ }
+
+ // This object can be used only once.
+ mAlreadyRead = true;
+
+ RefPtr<EntriesCallbackRunnable> runnable =
+ new EntriesCallbackRunnable(&aSuccessCallback, mEntries);
+ aRv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "NS_DispatchToMainThread failed");
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/filesystem/compat/FileSystemRootDirectoryReader.h b/dom/filesystem/compat/FileSystemRootDirectoryReader.h
new file mode 100644
index 000000000..54bca7726
--- /dev/null
+++ b/dom/filesystem/compat/FileSystemRootDirectoryReader.h
@@ -0,0 +1,41 @@
+/* -*- 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_FileSystemRootDirectoryReader_h
+#define mozilla_dom_FileSystemRootDirectoryReader_h
+
+#include "FileSystemDirectoryReader.h"
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemRootDirectoryReader final : public FileSystemDirectoryReader
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemRootDirectoryReader,
+ FileSystemDirectoryReader)
+
+ explicit FileSystemRootDirectoryReader(FileSystemDirectoryEntry* aParentEntry,
+ FileSystem* aFileSystem,
+ const Sequence<RefPtr<FileSystemEntry>>& aEntries);
+
+ virtual void
+ ReadEntries(FileSystemEntriesCallback& aSuccessCallback,
+ const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+ ErrorResult& aRv) override;
+
+private:
+ ~FileSystemRootDirectoryReader();
+
+ Sequence<RefPtr<FileSystemEntry>> mEntries;
+ bool mAlreadyRead;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemRootDirectoryReader_h
diff --git a/dom/filesystem/compat/moz.build b/dom/filesystem/compat/moz.build
new file mode 100644
index 000000000..fc3f9482f
--- /dev/null
+++ b/dom/filesystem/compat/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+TEST_DIRS += ['tests']
+
+EXPORTS.mozilla.dom += [
+ 'FileSystem.h',
+ 'FileSystemDirectoryEntry.h',
+ 'FileSystemDirectoryReader.h',
+ 'FileSystemEntry.h',
+ 'FileSystemFileEntry.h',
+]
+
+UNIFIED_SOURCES += [
+ 'CallbackRunnables.cpp',
+ 'FileSystem.cpp',
+ 'FileSystemDirectoryEntry.cpp',
+ 'FileSystemDirectoryReader.cpp',
+ 'FileSystemEntry.cpp',
+ 'FileSystemFileEntry.cpp',
+ 'FileSystemRootDirectoryEntry.cpp',
+ 'FileSystemRootDirectoryReader.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
diff --git a/dom/filesystem/compat/tests/mochitest.ini b/dom/filesystem/compat/tests/mochitest.ini
new file mode 100644
index 000000000..2b6eafb55
--- /dev/null
+++ b/dom/filesystem/compat/tests/mochitest.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ script_entries.js
+ !/dom/html/test/form_submit_server.sjs
+
+[test_basic.html]
+[test_no_dnd.html]
+[test_formSubmission.html]
diff --git a/dom/filesystem/compat/tests/moz.build b/dom/filesystem/compat/tests/moz.build
new file mode 100644
index 000000000..3b13ba431
--- /dev/null
+++ b/dom/filesystem/compat/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
diff --git a/dom/filesystem/compat/tests/script_entries.js b/dom/filesystem/compat/tests/script_entries.js
new file mode 100644
index 000000000..8083214c9
--- /dev/null
+++ b/dom/filesystem/compat/tests/script_entries.js
@@ -0,0 +1,47 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File", "Directory"]);
+
+var tmpFile, tmpDir;
+
+addMessageListener("entries.open", function (e) {
+ tmpFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get('TmpD', Ci.nsIFile)
+ tmpFile.append('file.txt');
+ tmpFile.createUnique(Components.interfaces.nsIFile.FILE_TYPE, 0o600);
+
+ tmpDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get('TmpD', Ci.nsIFile)
+
+ tmpDir.append('dir-test');
+ tmpDir.createUnique(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+ var file1 = tmpDir.clone();
+ file1.append('foo.txt');
+ file1.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ var dir1 = tmpDir.clone();
+ dir1.append('subdir');
+ dir1.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+ var file2 = dir1.clone();
+ file2.append('bar..txt'); // Note the double ..
+ file2.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ var dir2 = dir1.clone();
+ dir2.append('subsubdir');
+ dir2.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+ sendAsyncMessage("entries.opened", {
+ data: [ new Directory(tmpDir.path), File.createFromNsIFile(tmpFile) ]
+ });
+});
+
+addMessageListener("entries.delete", function(e) {
+ tmpFile.remove(true);
+ tmpDir.remove(true);
+ sendAsyncMessage("entries.deleted");
+});
diff --git a/dom/filesystem/compat/tests/test_basic.html b/dom/filesystem/compat/tests/test_basic.html
new file mode 100644
index 000000000..85a7418d5
--- /dev/null
+++ b/dom/filesystem/compat/tests/test_basic.html
@@ -0,0 +1,494 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Blink FileSystem API - subset</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="entries" type="file"></input>
+<script type="application/javascript;version=1.7">
+
+var fileEntry;
+var directoryEntry;
+var script;
+
+function setup_tests() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true],
+ ["dom.filesystem.pathcheck.disabled", true],
+ ["dom.webkitBlink.filesystem.enabled", true]]}, next);
+}
+
+function populate_entries() {
+ var url = SimpleTest.getTestFileURL("script_entries.js");
+ script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ var entries = document.getElementById('entries');
+ SpecialPowers.wrap(entries).mozSetDndFilesAndDirectories(message.data);
+ next();
+ }
+
+ script.addMessageListener("entries.opened", onOpened);
+ script.sendAsyncMessage("entries.open");
+}
+
+function test_entries() {
+ var entries = document.getElementById('entries');
+ ok("webkitEntries" in entries, "HTMLInputElement.webkitEntries");
+ is(entries.webkitEntries.length, 2, "HTMLInputElement.webkitEntries.length == 2");
+ is(entries.files.length, 1, "HTMLInputElement.files is still populated");
+
+ for (var i = 0; i < entries.webkitEntries.length; ++i) {
+ if (entries.webkitEntries[i].isFile) {
+ ok(!fileEntry, "We just want 1 fileEntry");
+ fileEntry = entries.webkitEntries[i];
+ } else {
+ ok(entries.webkitEntries[i].isDirectory, "If not a file, we have a directory.");
+ ok(!directoryEntry, "We just want 1 directoryEntry");
+ directoryEntry = entries.webkitEntries[i];
+ }
+ }
+
+ next();
+}
+
+function test_fileEntry() {
+ ok("name" in fileEntry, "We have a name.");
+ ok("fullPath" in fileEntry, "We have a fullPath.");
+ ok("filesystem" in fileEntry, "We have a filesystem.");
+
+ next();
+}
+
+function test_fileEntry_file() {
+ fileEntry.file(function(file) {
+ ok(file, "We have a file here!");
+ is(file.name, fileEntry.name, "Same file name.");
+ next();
+ }, function() {
+ ok(false, "Something when wrong!");
+ });
+}
+
+function test_fileEntry_getParent() {
+ fileEntry.getParent(function(entry) {
+ is(fileEntry.fullPath, entry.fullPath, "Top level FileEntry should return itself as parent.");
+ next();
+ }, function() {
+ ok(false, "This is wrong.");
+ });
+}
+
+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!");
+ });
+}
+
+function test_directoryEntry_getParent() {
+ directoryEntry.getParent(function(entry) {
+ is(directoryEntry.fullPath, entry.fullPath, "Top level FileEntry should return itself as parent.");
+ next();
+ }, function() {
+ ok(false, "This is wrong.");
+ });
+}
+
+function test_directoryEntry_getFile_securityError() {
+ directoryEntry.getFile("foo", { create: true },
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "SecurityError", "This must generate a SecurityError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getFile_typeMismatchError() {
+ directoryEntry.getFile("subdir", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getFile_nonValidPath() {
+ directoryEntry.getFile("../../", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getFile_nonExistingPath() {
+ directoryEntry.getFile("foo_bar.txt", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getFile_simple() {
+ directoryEntry.getFile("foo.txt", {},
+ function(e) {
+ is(e.name, "foo.txt", "We have the right FileEntry.");
+ test_getParent(e, directoryEntry, /* nested */ false);
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_directoryEntry_getFile_deep() {
+ directoryEntry.getFile("subdir/bar..txt", {},
+ function(e) {
+ is(e.name, "bar..txt", "We have the right FileEntry.");
+ test_getParent(e, directoryEntry, /* nested */ true);
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_directoryEntry_getDirectory_securityError() {
+ directoryEntry.getDirectory("foo", { create: true },
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "SecurityError", "This must generate a SecurityError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getDirectory_typeMismatchError() {
+ directoryEntry.getDirectory("foo.txt", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getDirectory_nonValidPath() {
+ directoryEntry.getDirectory("../../", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getDirectory_nonExistingPath() {
+ directoryEntry.getDirectory("non_existing_dir", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_directoryEntry_getDirectory_simple() {
+ directoryEntry.getDirectory("subdir", {},
+ function(e) {
+ is(e.name, "subdir", "We have the right DirectoryEntry.");
+ test_getParent(e, directoryEntry, /* nested */ false);
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_directoryEntry_getDirectory_deep() {
+ directoryEntry.getDirectory("subdir/subsubdir", {},
+ function(e) {
+ is(e.name, "subsubdir", "We have the right DirectoryEntry.");
+ test_getParent(e, directoryEntry, /* nested */ true);
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_filesystem() {
+ is(fileEntry.filesystem, directoryEntry.filesystem, "FileSystem object is shared.");
+
+ var fs = fileEntry.filesystem;
+ ok(fs.name, "FileSystem.name exists.");
+ ok(fs.root, "FileSystem has a root.");
+
+ is(fs.root.name, "", "FileSystem.root.name must be an empty string.");
+ is(fs.root.fullPath, "/", "FileSystem.root.fullPath must be '/'");
+
+ reader = fs.root.createReader();
+ reader.readEntries(function(a) {
+ ok(Array.isArray(a), "We want an array.");
+ is(a.length, 2, "reader.readyEntries returns 2 elements.");
+ next();
+ }, function() {
+ ok(false, "Something when wrong!");
+ });
+}
+
+function test_root_getFile_securityError() {
+ fileEntry.filesystem.root.getFile("foo", { create: true },
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "SecurityError", "This must generate a SecurityError.");
+ next();
+ });
+}
+
+function test_root_getFile_typeMismatchError() {
+ fileEntry.filesystem.root.getFile(directoryEntry.name, {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+ next();
+ });
+}
+
+function test_root_getFile_nonValidPath() {
+ fileEntry.filesystem.root.getFile("../../", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_root_getFile_nonExistingPath() {
+ fileEntry.filesystem.root.getFile("existing.txt", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_root_getFile_simple() {
+ fileEntry.filesystem.root.getFile(fileEntry.name, {},
+ function(e) {
+ is(e.name, fileEntry.name, "We have the right FileEntry.");
+ next();
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_root_getFile_deep() {
+ fileEntry.filesystem.root.getFile(directoryEntry.name + "/subdir/bar..txt", {},
+ function(e) {
+ is(e.name, "bar..txt", "We have the right FileEntry.");
+ next();
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_root_getDirectory_securityError() {
+ fileEntry.filesystem.root.getDirectory("foo", { create: true },
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "SecurityError", "This must generate a SecurityError.");
+ next();
+ });
+}
+
+function test_root_getDirectory_typeMismatchError() {
+ fileEntry.filesystem.root.getDirectory(fileEntry.name, {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+ next();
+ });
+}
+
+function test_root_getDirectory_nonValidPath() {
+ fileEntry.filesystem.root.getDirectory("../../", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_root_getDirectory_nonExistingPath() {
+ fileEntry.filesystem.root.getDirectory("404", {},
+ function() {
+ ok(false, "This should not happen.");
+ }, function(e) {
+ is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+ next();
+ });
+}
+
+function test_root_getDirectory_simple() {
+ fileEntry.filesystem.root.getDirectory(directoryEntry.name, {},
+ function(e) {
+ is(e.name, directoryEntry.name, "We have the right DirectoryEntry.");
+ next();
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_root_getDirectory_deep() {
+ fileEntry.filesystem.root.getDirectory(directoryEntry.name + "/subdir/subsubdir", {},
+ function(e) {
+ is(e.name, "subsubdir", "We have the right DirectoryEntry.");
+ next();
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function cleanUpTestingFiles() {
+ script.addMessageListener("entries.deleted", function onDeleted() {
+ script.removeMessageListener("entries.deleted");
+ script.destroy();
+ next();
+ });
+
+ script.sendAsyncMessage("entries.delete");
+}
+
+function test_getParent(entry, parentEntry, nested) {
+ entry.getParent(function(e) {
+ ok(e, "We have a parent Entry.");
+ if (!nested) {
+ is (e, parentEntry, "Parent entry matches");
+ next();
+ } else {
+ test_getParent(e, parentEntry, false);
+ }
+ }, function(e) {
+ ok(false, "This should not happen.");
+ });
+}
+
+function test_webkitRelativePath() {
+ fileEntry.file(function(file1) {
+ ok(file1, "We have a file here!");
+ ok(!file1.webkitRelativePath, "webkitRelativePath is an empty string");
+
+ fileEntry.file(function(file2) {
+ ok(file2, "We have a file here!");
+ ok(!file2.webkitRelativePath, "webkitRelativePath is an empty string");
+ isnot(file1, file2, "The 2 files are not the same");
+
+ 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_getParent,
+
+ test_directoryEntry,
+ test_directoryEntry_createReader,
+ test_directoryEntry_getParent,
+
+ test_directoryEntry_getFile_securityError,
+ test_directoryEntry_getFile_typeMismatchError,
+ test_directoryEntry_getFile_nonValidPath,
+ test_directoryEntry_getFile_nonExistingPath,
+ test_directoryEntry_getFile_simple,
+ test_directoryEntry_getFile_deep,
+
+ test_directoryEntry_getDirectory_securityError,
+ test_directoryEntry_getDirectory_typeMismatchError,
+ test_directoryEntry_getDirectory_nonValidPath,
+ test_directoryEntry_getDirectory_nonExistingPath,
+ test_directoryEntry_getDirectory_simple,
+ test_directoryEntry_getDirectory_deep,
+
+ test_filesystem,
+
+ test_root_getFile_securityError,
+ test_root_getFile_typeMismatchError,
+ test_root_getFile_nonValidPath,
+ test_root_getFile_nonExistingPath,
+ test_root_getFile_simple,
+ test_root_getFile_deep,
+
+ test_root_getDirectory_securityError,
+ test_root_getDirectory_typeMismatchError,
+ test_root_getDirectory_nonValidPath,
+ test_root_getDirectory_nonExistingPath,
+ test_root_getDirectory_simple,
+ test_root_getDirectory_deep,
+
+ test_webkitRelativePath,
+
+ cleanUpTestingFiles,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
diff --git a/dom/filesystem/compat/tests/test_formSubmission.html b/dom/filesystem/compat/tests/test_formSubmission.html
new file mode 100644
index 000000000..0c04b8bf1
--- /dev/null
+++ b/dom/filesystem/compat/tests/test_formSubmission.html
@@ -0,0 +1,267 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Directory form submission</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+</head>
+<body onload="return next();">
+
+<iframe name="target_iframe" id="target_iframe"></iframe>
+
+<form action="../../../html/test/form_submit_server.sjs" target="target_iframe" id="form"
+ method="POST" enctype="multipart/form-data">
+</form>
+
+<script class="testbody" type="text/javascript;version=1.8">
+
+var form;
+var iframe;
+var input;
+var xhr;
+
+function setup_tests() {
+ form = document.getElementById("form");
+
+ iframe = document.getElementById("target_iframe");
+ iframe.onload = function() {
+ info("Frame loaded!");
+ next();
+ }
+
+ SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+ ["dom.webkitBlink.dirPicker.enabled", true],
+ ["dom.filesystem.pathcheck.disabled", true],
+ ["dom.webkitBlink.filesystem.enabled", true]]}, next);
+}
+
+function populate_entries(webkitDirectory) {
+ if (input) {
+ form.removeChild(input);
+ }
+
+ input = document.createElement('input');
+ input.setAttribute('id', 'input');
+ input.setAttribute('type', 'file');
+ input.setAttribute('name', 'input');
+
+ if (webkitDirectory) {
+ input.setAttribute('webkitdirectory', 'true');
+ }
+
+ form.appendChild(input);
+
+ var url = SimpleTest.getTestFileURL("script_entries.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ input.addEventListener("change", function onChange() {
+ input.removeEventListener("change", onChange);
+ next();
+ });
+
+ SpecialPowers.wrap(input).mozSetDndFilesAndDirectories([message.data[0]]);
+ script.destroy();
+ }
+
+ script.addMessageListener("entries.opened", onOpened);
+ script.sendAsyncMessage("entries.open");
+}
+
+function setup_plain() {
+ info("Preparing for a plain text submission...");
+ form.action = "../../../html/test/form_submit_server.sjs?plain";
+ form.method = "POST";
+ form.enctype = "text/plain";
+ form.submit();
+}
+
+function test_plain() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+ input.getFilesAndDirectories().then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name + "\r\n";
+ }).join(""), "Data match");
+
+ next();
+ });
+}
+
+function setup_urlencoded() {
+ info("Preparing for a urlencoded submission...");
+ form.action = "../../../html/test/form_submit_server.sjs?url";
+ form.method = "POST";
+ form.enctype = "application/x-www-form-urlencoded";
+ form.submit();
+}
+
+function setup_urlencoded_get() {
+ info("Preparing for a urlencoded+GET submission...");
+ form.action = "../../../html/test/form_submit_server.sjs?xxyy";
+ form.method = "GET";
+ form.enctype = "";
+ form.submit();
+}
+
+function setup_urlencoded_empty() {
+ info("Preparing for a urlencoded+default values submission...");
+ form.action = "../../../html/test/form_submit_server.sjs";
+ form.method = "";
+ form.enctype = "";
+ form.submit();
+}
+
+function test_urlencoded() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+ input.getFilesAndDirectories().then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name;
+ }).join("&"), "Data match");
+
+ next();
+ });
+}
+
+function setup_formData() {
+ info("Preparing for a fromData submission...");
+
+ xhr = new XMLHttpRequest();
+ xhr.onload = next;
+ xhr.open("POST", "../../../html/test/form_submit_server.sjs");
+ xhr.send(new FormData(form));
+}
+
+function test_multipart() {
+ var submission = JSON.parse(xhr.responseText);
+ input.getFilesAndDirectories().then(function(array) {
+ is(submission.length, array.length, "Same length");
+
+ for (var i = 0; i < array.length; ++i) {
+ if (array[i] instanceof Directory) {
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"/" + array[i].name + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], "application/octet-stream",
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ } else {
+ ok(array[i] instanceof File);
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"" + array[i].name + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], array[i].type,
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ }
+ }
+ next();
+ });
+}
+
+function test_webkit_plain() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+
+ input.getFiles(true).then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name + "\r\n";
+ }).join(""), "Data match");
+
+ next();
+ });
+}
+
+function test_webkit_urlencoded() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+ input.getFiles(true).then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name;
+ }).join("&"), "Data match");
+
+ next();
+ });
+}
+
+function test_webkit_multipart() {
+ var submission = JSON.parse(xhr.responseText);
+ input.getFiles(true).then(function(array) {
+ is(submission.length, array.length, "Same length");
+
+ for (var i = 0; i < array.length; ++i) {
+ if (array[i] instanceof Directory) {
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"/" + array[i].name + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], "application/octet-stream",
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ } else {
+ ok(array[i] instanceof File);
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"" + array[i].webkitRelativePath + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], array[i].type,
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ }
+ }
+ next();
+ });
+}
+
+var tests = [
+ setup_tests,
+ function() { populate_entries(false); },
+
+ setup_plain,
+ test_plain,
+
+ setup_urlencoded,
+ test_urlencoded,
+
+ setup_urlencoded_get,
+ test_urlencoded,
+
+ setup_urlencoded_empty,
+ test_urlencoded,
+
+ setup_formData,
+ test_multipart,
+
+ function() { populate_entries(true); },
+
+ setup_plain,
+ test_webkit_plain,
+
+ setup_urlencoded,
+ test_webkit_urlencoded,
+
+ setup_urlencoded_get,
+ test_webkit_urlencoded,
+
+ setup_urlencoded_empty,
+ test_webkit_urlencoded,
+
+ setup_formData,
+ test_webkit_multipart,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/filesystem/compat/tests/test_no_dnd.html b/dom/filesystem/compat/tests/test_no_dnd.html
new file mode 100644
index 000000000..a78ac108f
--- /dev/null
+++ b/dom/filesystem/compat/tests/test_no_dnd.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Blink FileSystem API - no DND == no webkitEntries</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<script type="application/javascript;version=1.7">
+
+var fileEntry;
+var directoryEntry;
+var script;
+var entries;
+
+function setup_tests() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true],
+ ["dom.filesystem.pathcheck.disabled", true],
+ ["dom.webkitBlink.filesystem.enabled", true]]}, next);
+}
+
+function populate_entries() {
+ entries = document.createElement('input');
+ entries.setAttribute('type', 'file');
+ document.body.appendChild(entries);
+
+ var url = SimpleTest.getTestFileURL("script_entries.js");
+ script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ for (var i = 0 ; i < message.data.length; ++i) {
+ if (message.data[i] instanceof File) {
+ SpecialPowers.wrap(entries).mozSetFileArray([message.data[i]]);
+ next();
+ }
+ }
+ }
+
+ script.addMessageListener("entries.opened", onOpened);
+ script.sendAsyncMessage("entries.open");
+}
+
+function test_entries() {
+ ok("webkitEntries" in entries, "HTMLInputElement.webkitEntries");
+ is(entries.webkitEntries.length, 0, "HTMLInputElement.webkitEntries.length == 0");
+ is(entries.files.length, 1, "HTMLInputElement.files is still populated");
+
+ next();
+}
+
+function cleanUpTestingFiles() {
+ script.addMessageListener("entries.deleted", function onDeleted() {
+ script.removeMessageListener("entries.deleted");
+ script.destroy();
+ next();
+ });
+
+ script.sendAsyncMessage("entries.delete");
+}
+
+var tests = [
+ setup_tests,
+ populate_entries,
+
+ test_entries,
+
+ cleanUpTestingFiles,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
diff --git a/dom/filesystem/moz.build b/dom/filesystem/moz.build
new file mode 100644
index 000000000..6e516c3a7
--- /dev/null
+++ b/dom/filesystem/moz.build
@@ -0,0 +1,48 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['compat']
+
+TEST_DIRS += ['tests']
+
+EXPORTS.mozilla.dom += [
+ 'Directory.h',
+ 'FileSystemBase.h',
+ 'FileSystemRequestParent.h',
+ 'FileSystemSecurity.h',
+ 'FileSystemTaskBase.h',
+ 'FileSystemUtils.h',
+ 'GetFilesHelper.h',
+ 'OSFileSystem.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Directory.cpp',
+ 'FileSystemBase.cpp',
+ 'FileSystemRequestParent.cpp',
+ 'FileSystemSecurity.cpp',
+ 'FileSystemTaskBase.cpp',
+ 'FileSystemUtils.cpp',
+ 'GetDirectoryListingTask.cpp',
+ 'GetFileOrDirectoryTask.cpp',
+ 'GetFilesHelper.cpp',
+ 'GetFilesTask.cpp',
+ 'OSFileSystem.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+IPDL_SOURCES += [
+ 'PFileSystemParams.ipdlh',
+ 'PFileSystemRequest.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/workers',
+]
diff --git a/dom/filesystem/tests/filesystem_commons.js b/dom/filesystem/tests/filesystem_commons.js
new file mode 100644
index 000000000..4f7234121
--- /dev/null
+++ b/dom/filesystem/tests/filesystem_commons.js
@@ -0,0 +1,103 @@
+function createPath(parentDir, dirOrFile) {
+ return parentDir.path + (parentDir.path == '/' ? '' : '/') + dirOrFile.name;
+}
+
+function createRelativePath(parentDir, dirOrFile) {
+ let path = createPath(parentDir, dirOrFile);
+ is(path[0], "/", "The full path should start with '/'");
+ return path.substring(1);
+}
+
+function setup_tests(aNext) {
+ SimpleTest.requestLongerTimeout(2);
+ SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+ ["dom.filesystem.pathcheck.disabled", true],
+ ["dom.webkitBlink.dirPicker.enabled", true]]}, aNext);
+}
+
+function test_basic(aDirectory, aNext) {
+ ok(aDirectory, "Directory exists.");
+ ok(aDirectory instanceof Directory, "We have a directory.");
+ is(aDirectory.path, '/' + aDirectory.name, "directory.path must be '/'+name");
+ 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, createPath(dir, data[i]), "Subdirectory path should be called parentdir.path + '/' + leafname: " + data[i].path);
+ }
+
+ if (data[i] instanceof File) {
+ is(data[i].webkitRelativePath, createRelativePath(dir, data[i]), "File.webkitRelativePath should be called: parentdir.path + '/' + file.name: " + data[i].webkitRelativePath);
+ }
+ }
+ }
+ );
+ }
+
+ 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, createPath(aDirectory, data[i]), "Subdirectory path should be called parentdir.path + '/' + leafname: " + data[i].path);
+ if (aRecursive) {
+ promises.push(checkSubDir(data[i]));
+ }
+ }
+
+ if (data[i] instanceof File) {
+ is(data[i].webkitRelativePath, createRelativePath(aDirectory, data[i]), "File.webkitRelativePath should be called file.name: " + data[i].webkitRelativePath);
+ }
+ }
+
+ 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);
+ is(aDirectory.path[0], '/', "Directory path must start with '/'");
+ ok(data[i].webkitRelativePath.indexOf(aDirectory.path.substring(1)) == 0 &&
+ data[i].webkitRelativePath.indexOf('/' + data[i].name) + ('/' + data[i].name).length == data[i].webkitRelativePath.length,
+ "File.webkitRelativePath should be called dir.path + '/' + file.name: " + data[i].webkitRelativePath);
+ }
+ },
+ 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);
+}
diff --git a/dom/filesystem/tests/mochitest.ini b/dom/filesystem/tests/mochitest.ini
new file mode 100644
index 000000000..649016886
--- /dev/null
+++ b/dom/filesystem/tests/mochitest.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ filesystem_commons.js
+ script_fileList.js
+ worker_basic.js
+
+[test_basic.html]
+[test_webkitdirectory.html]
+[test_worker_basic.html]
+[test_bug1319088.html]
diff --git a/dom/filesystem/tests/moz.build b/dom/filesystem/tests/moz.build
new file mode 100644
index 000000000..3b13ba431
--- /dev/null
+++ b/dom/filesystem/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
diff --git a/dom/filesystem/tests/script_fileList.js b/dom/filesystem/tests/script_fileList.js
new file mode 100644
index 000000000..89fd04cab
--- /dev/null
+++ b/dom/filesystem/tests/script_fileList.js
@@ -0,0 +1,129 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File"]);
+
+function createProfDFile() {
+ return Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get('ProfD', Ci.nsIFile);
+}
+
+// Creates a parametric arity directory hierarchy as a function of depth.
+// Each directory contains one leaf file, and subdirectories of depth [1, depth).
+// e.g. for depth 3:
+//
+// subdir3
+// - file.txt
+// - subdir2
+// - file.txt
+// - subdir1
+// - file.txt
+// - subdir1
+// - file.txt
+//
+// Returns the parent directory of the subtree.
+function createTreeFile(depth, parent) {
+ if (!parent) {
+ parent = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get('TmpD', Ci.nsIFile);
+ parent.append('dir-tree-test');
+ parent.createUnique(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+ }
+
+ var nextFile = parent.clone();
+ if (depth == 0) {
+ nextFile.append('file.txt');
+ nextFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ } else {
+ nextFile.append('subdir' + depth);
+ nextFile.createUnique(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+ // Decrement the maximal depth by one for each level of nesting.
+ for (i = 0; i < depth; i++) {
+ createTreeFile(i, nextFile);
+ }
+ }
+
+ return parent;
+}
+
+function createRootFile() {
+ var testFile = createProfDFile();
+
+ // Let's go back to the root of the FileSystem
+ 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);
+
+ 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':
+ // Note that files in the profile directory are not guaranteed to persist-
+ // see bug 1284742.
+ testFile = createProfDFile();
+ break;
+
+ case 'root':
+ testFile = createRootFile();
+ break;
+
+ case 'test':
+ testFile = createTestFile();
+ break;
+
+ case 'tree':
+ testFile = createTreeFile(3);
+ break;
+ }
+
+ sendAsyncMessage("dir.opened", {
+ dir: testFile.path,
+ name: testFile.leafName
+ });
+});
+
+addMessageListener("file.open", function (e) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ testFile.append("prefs.js");
+
+ sendAsyncMessage("file.opened", {
+ file: File.createFromNsIFile(testFile)
+ });
+});
diff --git a/dom/filesystem/tests/test_basic.html b/dom/filesystem/tests/test_basic.html
new file mode 100644
index 000000000..1308b9e3c
--- /dev/null
+++ b/dom/filesystem/tests/test_basic.html
@@ -0,0 +1,167 @@
+<!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>
+<script type="application/javascript;version=1.7">
+
+var directory;
+var fileList;
+
+function create_fileList(aPath) {
+ fileList = document.createElement('input');
+ fileList.setAttribute('type', 'file');
+ document.body.appendChild(fileList);
+
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+ fileList.setAttribute('data-name', message.name);
+
+ fileList.getFilesAndDirectories().then(function(array) {
+ is(array.length, 1, "We want just 1 directory.");
+ ok(array[0] instanceof Directory, "We want just 1 directory.");
+
+ directory = array[0];
+ script.destroy();
+ next();
+ });
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: aPath });
+}
+
+function test_simpleFilePicker(aPath) {
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ SpecialPowers.wrap(fileList).mozSetFileArray([message.file]);
+
+ is(fileList.files.length, 1, "we want 1 element");
+ ok(fileList.files[0] instanceof File, "we want 1 file");
+ ok("webkitRelativePath" in fileList.files[0], "we have webkitRelativePath attribute");
+ is(fileList.files[0].webkitRelativePath, "", "No webkit relative path for normal filePicker");
+
+ script.destroy();
+ next();
+ }
+
+ script.addMessageListener("file.opened", onOpened);
+ script.sendAsyncMessage("file.open");
+}
+
+function test_duplicateGetFilesAndDirectories() {
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+
+ var p1 = fileList.getFilesAndDirectories();
+ var p2 = fileList.getFilesAndDirectories();
+
+ isnot(p1, p2, "We create 2 different promises");
+
+ script.destroy();
+ next();
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: 'test' });
+}
+
+function test_inputGetFiles() {
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+ fileList.setAttribute('data-name', message.name);
+
+ fileList.getFilesAndDirectories()
+ .then(function(result) {
+ is(result.length, 1, "getFilesAndDirectories should return 1 element");
+ ok(result[0] instanceof Directory, "getFilesAndDirectories should return 1 directory");
+
+ return fileList.getFiles(false);
+ })
+ .then(function(result) {
+ is(result.length, 1, "getFiles should return 1 element");
+ ok(result[0] instanceof File, "getFile should return 1 file");
+ is(result[0].name, 'foo.txt', "getFiles()[0].name should be 'foo.txt'");
+ is(result[0].webkitRelativePath, fileList.dataset.name + '/foo.txt', "getFiles()[0].webkitRelativePath should be '/foo.txt'");
+
+ return fileList.getFiles(true);
+ })
+ .then(function(result) {
+ is(result.length, 2, "getFiles should return 2 elements");
+
+ function checkFile(file) {
+ ok(file instanceof File, "getFile[x] should return a file");
+ if (file.name == 'foo.txt') {
+ is(file.webkitRelativePath, fileList.dataset.name + '/foo.txt', "getFiles()[x].webkitRelativePath should be '/foo.txt'");
+ } else {
+ is(file.name, 'bar.txt', "getFiles()[x].name should be 'bar.txt'");
+ is(file.webkitRelativePath, fileList.dataset.name + '/subdir/bar.txt', "getFiles()[x].webkitRelativePath should be '/subdir/bar.txt'");
+ }
+ }
+
+ checkFile(result[0]);
+ checkFile(result[1]);
+ })
+ .then(function() {
+ script.destroy();
+ next();
+ });
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: 'test' });
+}
+
+var tests = [
+ function() { setup_tests(next); },
+
+ function() { create_fileList('tree') },
+ 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('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); },
+
+ test_duplicateGetFilesAndDirectories,
+ test_inputGetFiles,
+ test_simpleFilePicker
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
diff --git a/dom/filesystem/tests/test_bug1319088.html b/dom/filesystem/tests/test_bug1319088.html
new file mode 100644
index 000000000..e0a53223f
--- /dev/null
+++ b/dom/filesystem/tests/test_bug1319088.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1319088</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="input" type="file"></input>
+
+<script type="application/javascript;version=1.7">
+
+function testSetup() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+ ["dom.webkitBlink.dirPicker.enabled", true]]}, next);
+}
+
+function populateInputFile() {
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ var input = document.getElementById("input");
+ SpecialPowers.wrap(input).mozSetFileArray([message.file]);
+
+ script.destroy();
+ next();
+ }
+
+ script.addMessageListener("file.opened", onOpened);
+ script.sendAsyncMessage("file.open");
+}
+
+function checkBug() {
+ var input = document.getElementById("input");
+ is(input.files[0].webkitRelativePath, "", "No relative path!");
+
+ let form = document.createElement('form');
+ form.appendChild(input);
+
+ is(input.files[0].webkitRelativePath, "", "No relative path!");
+ SimpleTest.finish();
+}
+
+var tests = [
+ testSetup,
+ populateInputFile,
+ checkBug,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
diff --git a/dom/filesystem/tests/test_webkitdirectory.html b/dom/filesystem/tests/test_webkitdirectory.html
new file mode 100644
index 000000000..825f5e8fb
--- /dev/null
+++ b/dom/filesystem/tests/test_webkitdirectory.html
@@ -0,0 +1,191 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for webkitdirectory and webkitRelativePath</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="inputFileWebkitDirectory" type="file" webkitdirectory></input>
+<input id="inputFileWebkitDirectoryAndDirectory" type="file" webkitdirectory allowdirs></input>
+<input id="inputFileDirectory" type="file" allowdirs></input>
+<input id="inputFileDirectoryChange" type="file" webkitdirectory></input>
+
+<script type="application/javascript;version=1.7">
+
+function populateInputFile(aInputFile) {
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ var MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
+
+ function onOpened(message) {
+ MockFilePicker.useDirectory(message.dir);
+
+ var input = document.getElementById(aInputFile);
+ input.setAttribute('data-name', message.name);
+ input.addEventListener('change', function change() {
+ input.removeEventListener('change', change);
+ MockFilePicker.cleanup();
+ script.destroy();
+ next();
+ });
+
+ input.click();
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: 'test' });
+}
+
+function checkFile(file, fileList, dirName) {
+ for (var i = 0; i < fileList.length; ++i) {
+ ok(fileList[i] instanceof File, "We want just files.");
+ if (fileList[i].name == file.name) {
+ is(fileList[i].webkitRelativePath, dirName + file.path, "Path matches");
+ return;
+ }
+ }
+
+ ok(false, "File not found.");
+}
+
+function test_fileList(aInputFile, aWhat) {
+ var input = document.getElementById(aInputFile);
+ var fileList = input.files;
+
+ if (aWhat == null) {
+ is(fileList, null, "We want a null fileList for " + aInputFile);
+ next();
+ return;
+ }
+
+ is(fileList.length, aWhat.length, "We want just " + aWhat.length + " elements for " + aInputFile);
+ for (var i = 0; i < aWhat.length; ++i) {
+ checkFile(aWhat[i], fileList, input.dataset.name);
+ }
+
+ next();
+}
+
+function test_webkitdirectory_attribute() {
+ var a = document.createElement("input");
+ a.setAttribute("type", "file");
+
+ ok("webkitdirectory" in a, "HTMLInputElement.webkitdirectory exists");
+
+ ok(!a.hasAttribute("webkitdirectory"), "No webkitdirectory DOM attribute by default");
+ ok(!a.webkitdirectory, "No webkitdirectory attribute by default");
+
+ a.webkitdirectory = true;
+
+ ok(a.hasAttribute("webkitdirectory"), "Webkitdirectory DOM attribute is set");
+ ok(a.webkitdirectory, "Webkitdirectory attribute is set");
+
+ next();
+}
+
+function test_changeDataWhileWorking() {
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ var MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
+
+ // Let's start retrieving the root nsIFile object
+ new Promise(function(resolve) {
+ function onOpened(message) {
+ script.removeMessageListener("dir.opened", onOpened);
+ resolve(message.dir);
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: 'root' });
+ })
+
+ // input.click() pointing to the root dir
+ .then(function(aDir) {
+ MockFilePicker.cleanup();
+ MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
+ MockFilePicker.useDirectory(aDir);
+ var input = document.getElementById("inputFileDirectoryChange");
+ input.click();
+ })
+
+ // Before onchange, let's take the 'test' directory
+ .then(function() {
+ return new Promise(function(resolve) {
+ function onOpened(message) {
+ script.removeMessageListener("dir.opened", onOpened);
+ script.destroy();
+ resolve(message.dir);
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: 'test' });
+ });
+ })
+
+ // Now let's click again and wait for onchange.
+ .then(function(aDir) {
+ return new Promise(function(resolve) {
+ MockFilePicker.cleanup();
+ MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
+ MockFilePicker.useDirectory(aDir);
+
+ var input = document.getElementById("inputFileDirectoryChange");
+ input.addEventListener('change', function() {
+ MockFilePicker.cleanup();
+ resolve();
+ });
+
+ input.click();
+ })
+ })
+
+ .then(function() {
+ test_fileList('inputFileWebkitDirectory', testDirData);
+ });
+}
+
+function test_setup() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+ ["dom.webkitBlink.dirPicker.enabled", true]]}, next);
+}
+
+var testDirData = [ { name: 'foo.txt', path: '/foo.txt' },
+ { name: 'bar.txt', path: '/subdir/bar.txt' }];
+
+var tests = [
+ test_setup,
+
+ function() { populateInputFile('inputFileWebkitDirectory'); },
+ function() { populateInputFile('inputFileWebkitDirectoryAndDirectory'); },
+ function() { populateInputFile('inputFileDirectory'); },
+
+ function() { test_fileList('inputFileWebkitDirectory', testDirData) },
+ function() { test_fileList('inputFileWebkitDirectoryAndDirectory', testDirData) },
+ function() { test_fileList('inputFileDirectory', null); },
+
+ test_webkitdirectory_attribute,
+
+ test_changeDataWhileWorking,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
diff --git a/dom/filesystem/tests/test_worker_basic.html b/dom/filesystem/tests/test_worker_basic.html
new file mode 100644
index 000000000..fd36faeeb
--- /dev/null
+++ b/dom/filesystem/tests/test_worker_basic.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Directory API in workers</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>
+<script type="application/javascript;version=1.7">
+
+var fileList;
+
+function create_fileList() {
+ fileList = document.createElement('input');
+ fileList.setAttribute('type', 'file');
+ document.body.appendChild(fileList);
+
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+ script.destroy();
+ next();
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: 'ProfD' });
+}
+
+function test_worker() {
+ fileList.getFilesAndDirectories().then(function(array) {
+ var worker = new Worker('worker_basic.js');
+ worker.onmessage = function(e) {
+ if (e.data.type == 'finish') {
+ next();
+ return;
+ }
+
+ if (e.data.type == 'test') {
+ ok(e.data.test, e.data.message);
+ }
+ }
+
+ worker.postMessage(array[0]);
+ });
+}
+
+var tests = [
+ function() { setup_tests(next); },
+
+ create_fileList,
+ test_worker,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
diff --git a/dom/filesystem/tests/worker_basic.js b/dom/filesystem/tests/worker_basic.js
new file mode 100644
index 000000000..01df3fbd1
--- /dev/null
+++ b/dom/filesystem/tests/worker_basic.js
@@ -0,0 +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);
+}
+
+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) {
+ directory = e.data;
+ next();
+}