diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index f583cf4881e1..c5af15b8d480 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -35,7 +35,8 @@ using blink::mojom::FileChooserParams; namespace { class FileSelectHelper : public base::RefCounted, - public content::WebContentsObserver { + public content::WebContentsObserver, + public atom::DirectoryListerHelperDelegate { public: FileSelectHelper(content::RenderFrameHost* render_frame_host, std::unique_ptr listener, @@ -71,23 +72,61 @@ class FileSelectHelper : public base::RefCounted, isolate, base::Bind(&FileSelectHelper::OnSaveDialogDone, this))))); } + void OnDirectoryListerDone(std::vector file_info, + base::FilePath base_dir) override { + OnFilesSelected(std::move(file_info), base_dir); + Release(); + } + private: friend class base::RefCounted; ~FileSelectHelper() override {} + void EnumerateDirectory(base::FilePath base_dir) { + auto* lister = new net::DirectoryLister( + base_dir, net::DirectoryLister::NO_SORT_RECURSIVE, + new atom::DirectoryListerHelper(base_dir, this)); + lister->Start(); + // It is difficult for callers to know how long to keep a reference to + // this instance. We AddRef() here to keep the instance alive after we + // return to the caller. Once the directory lister is complete we + // Release() in OnDirectoryListerDone() and at that point we run + // OnFilesSelected() which will deref the last reference held by the + // listener. + AddRef(); + } + void OnOpenDialogDone(mate::Dictionary result) { std::vector file_info; bool canceled = true; result.Get("canceled", &canceled); + base::FilePath base_dir; + // For certain file chooser modes (kUploadFolder) we need to do some async + // work before calling back to the listener. In that particular case the + // listener is called from the directory enumerator. + bool ready_to_call_listener = false; if (!canceled) { std::vector paths; if (result.Get("filePaths", &paths)) { - for (auto& path : paths) { - file_info.push_back(FileChooserFileInfo::NewNativeFile( - blink::mojom::NativeFileInfo::New( - path, path.BaseName().AsUTF16Unsafe()))); + // If we are uploading a folder we need to enumerate its contents + if (mode_ == FileChooserParams::Mode::kUploadFolder && + paths.size() >= 1) { + base_dir = paths[0]; + + // Actually enumerate soemwhere off-thread + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&FileSelectHelper::EnumerateDirectory, + this, base_dir)); + } else { + for (auto& path : paths) { + file_info.push_back(FileChooserFileInfo::NewNativeFile( + blink::mojom::NativeFileInfo::New( + path, path.BaseName().AsUTF16Unsafe()))); + } + + ready_to_call_listener = true; } if (render_frame_host_ && !paths.empty()) { @@ -98,7 +137,9 @@ class FileSelectHelper : public base::RefCounted, } } } - OnFilesSelected(std::move(file_info)); + + if (ready_to_call_listener) + OnFilesSelected(std::move(file_info), base_dir); } void OnSaveDialogDone(mate::Dictionary result) { @@ -114,12 +155,13 @@ class FileSelectHelper : public base::RefCounted, path, path.BaseName().AsUTF16Unsafe()))); } } - OnFilesSelected(std::move(file_info)); + OnFilesSelected(std::move(file_info), base::FilePath()); } - void OnFilesSelected(std::vector file_info) { + void OnFilesSelected(std::vector file_info, + base::FilePath base_dir) { if (listener_) { - listener_->FileSelected(std::move(file_info), base::FilePath(), mode_); + listener_->FileSelected(std::move(file_info), base_dir, mode_); listener_.reset(); } render_frame_host_ = nullptr; @@ -216,6 +258,30 @@ file_dialog::Filters GetFileTypesFromAcceptType( namespace atom { +DirectoryListerHelper::DirectoryListerHelper( + base::FilePath base, + DirectoryListerHelperDelegate* helper) + : base_dir_(base), delegate_(helper) {} +DirectoryListerHelper::~DirectoryListerHelper() {} + +void DirectoryListerHelper::OnListFile( + const net::DirectoryLister::DirectoryListerData& data) { + // We don't want to return directory paths, only file paths + if (data.info.IsDirectory()) + return; + + paths_.push_back(data.path); +} +void DirectoryListerHelper::OnListDone(int error) { + std::vector file_info; + for (auto path : paths_) + file_info.push_back(FileChooserFileInfo::NewNativeFile( + blink::mojom::NativeFileInfo::New(path, base::string16()))); + + delegate_->OnDirectoryListerDone(std::move(file_info), base_dir_); + delete this; +} + WebDialogHelper::WebDialogHelper(NativeWindow* window, bool offscreen) : window_(window), offscreen_(offscreen), weak_factory_(this) {} diff --git a/atom/browser/web_dialog_helper.h b/atom/browser/web_dialog_helper.h index 3574552d4a98..ccb7ab8ffa0d 100644 --- a/atom/browser/web_dialog_helper.h +++ b/atom/browser/web_dialog_helper.h @@ -6,8 +6,10 @@ #define ATOM_BROWSER_WEB_DIALOG_HELPER_H_ #include +#include #include "base/memory/weak_ptr.h" +#include "net/base/directory_lister.h" #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" namespace base { @@ -22,6 +24,32 @@ class WebContents; namespace atom { +class DirectoryListerHelperDelegate { + public: + virtual void OnDirectoryListerDone( + std::vector file_info, + base::FilePath base_dir) = 0; +}; + +class DirectoryListerHelper + : public net::DirectoryLister::DirectoryListerDelegate { + public: + DirectoryListerHelper(base::FilePath base, + DirectoryListerHelperDelegate* helper); + ~DirectoryListerHelper() override; + + private: + void OnListFile( + const net::DirectoryLister::DirectoryListerData& data) override; + void OnListDone(int error) override; + + base::FilePath base_dir_; + DirectoryListerHelperDelegate* delegate_; + std::vector paths_; + + DISALLOW_COPY_AND_ASSIGN(DirectoryListerHelper); +}; + class NativeWindow; class WebDialogHelper {