Add a path for a web page to request the enumeration of a directory.  This, together with a WebKit change, will allow a drag-and-drop on a Directory Upload control (<input type=file webkitdirectory>) which provides only the path to the renderer, to correctly populate the control as if the user had selected that directory in a file picker.

BUG=58977
TEST=drag-and-drop on directory upload control (with upstream change)
Review URL: http://codereview.chromium.org/6623015

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@81183 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/file_select_helper.cc b/chrome/browser/file_select_helper.cc
index c81ed2c..2ed69cd 100644
--- a/chrome/browser/file_select_helper.cc
+++ b/chrome/browser/file_select_helper.cc
@@ -12,6 +12,8 @@
 #include "base/utf_string_conversions.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
+#include "content/browser/child_process_security_policy.h"
+#include "content/browser/renderer_host/render_process_host.h"
 #include "content/browser/renderer_host/render_view_host.h"
 #include "content/browser/renderer_host/render_widget_host_view.h"
 #include "content/browser/tab_contents/tab_contents.h"
@@ -22,6 +24,14 @@
 #include "net/base/mime_util.h"
 #include "ui/base/l10n/l10n_util.h"
 
+namespace {
+
+// There is only one file-selection happening at any given time,
+// so we allocate an enumeration ID for that purpose.  All IDs from
+// the renderer must start at 0 and increase.
+static const int kFileSelectEnumerationId = -1;
+}
+
 FileSelectHelper::FileSelectHelper(Profile* profile)
     : profile_(profile),
       render_view_host_(NULL),
@@ -35,10 +45,17 @@
   if (select_file_dialog_.get())
     select_file_dialog_->ListenerDestroyed();
 
-  // Stop any pending directory enumeration and prevent a callback.
-  if (directory_lister_.get()) {
-    directory_lister_->set_delegate(NULL);
-    directory_lister_->Cancel();
+  // Stop any pending directory enumeration, prevent a callback, and free
+  // allocated memory.
+  std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
+  for (iter = directory_enumerations_.begin();
+       iter != directory_enumerations_.end();
+       ++iter) {
+    if (iter->second->lister_.get()) {
+      iter->second->lister_->set_delegate(NULL);
+      iter->second->lister_->Cancel();
+    }
+    delete iter->second;
   }
 }
 
@@ -50,7 +67,7 @@
   profile_->set_last_selected_directory(path.DirName());
 
   if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
-    DirectorySelected(path);
+    StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
     return;
   }
 
@@ -85,40 +102,55 @@
   render_view_host_ = NULL;
 }
 
-void FileSelectHelper::DirectorySelected(const FilePath& path) {
-  directory_lister_ = new net::DirectoryLister(path,
-                                               true,
-                                               net::DirectoryLister::NO_SORT,
-                                               this);
-  if (!directory_lister_->Start())
-    FileSelectionCanceled(NULL);
+void FileSelectHelper::StartNewEnumeration(const FilePath& path,
+                                           int request_id,
+                                           RenderViewHost* render_view_host) {
+  scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
+  entry->rvh_ = render_view_host;
+  entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
+  entry->lister_ = new net::DirectoryLister(path,
+                                            true,
+                                            net::DirectoryLister::NO_SORT,
+                                            entry->delegate_.get());
+  if (!entry->lister_->Start()) {
+    if (request_id == kFileSelectEnumerationId)
+      FileSelectionCanceled(NULL);
+    else
+      render_view_host->DirectoryEnumerationFinished(request_id,
+                                                     entry->results_);
+  } else {
+    directory_enumerations_[request_id] = entry.release();
+  }
 }
 
 void FileSelectHelper::OnListFile(
+    int id,
     const net::DirectoryLister::DirectoryListerData& data) {
+  ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
+
   // Directory upload returns directories via a "." file, so that
   // empty directories are included.  This util call just checks
   // the flags in the structure; there's no file I/O going on.
   if (file_util::FileEnumerator::IsDirectory(data.info))
-    directory_lister_results_.push_back(
-        data.path.Append(FILE_PATH_LITERAL(".")));
+    entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
   else
-    directory_lister_results_.push_back(data.path);
+    entry->results_.push_back(data.path);
 }
 
-void FileSelectHelper::OnListDone(int error) {
-  if (!render_view_host_)
+void FileSelectHelper::OnListDone(int id, int error) {
+  // This entry needs to be cleaned up when this function is done.
+  scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
+  directory_enumerations_.erase(id);
+  if (!entry->rvh_)
     return;
-
   if (error) {
     FileSelectionCanceled(NULL);
     return;
   }
-
-  render_view_host_->FilesSelectedInChooser(directory_lister_results_);
-  render_view_host_ = NULL;
-  directory_lister_ = NULL;
-  directory_lister_results_.clear();
+  if (id == kFileSelectEnumerationId)
+    entry->rvh_->FilesSelectedInChooser(entry->results_);
+  else
+    entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
 }
 
 SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
@@ -239,6 +271,13 @@
                                   NULL);
 }
 
+void FileSelectHelper::EnumerateDirectory(int request_id,
+                                          RenderViewHost* render_view_host,
+                                          const FilePath& path) {
+  DCHECK_NE(kFileSelectEnumerationId, request_id);
+  StartNewEnumeration(path, request_id, render_view_host);
+}
+
 void FileSelectHelper::Observe(NotificationType type,
                                const NotificationSource& source,
                                const NotificationDetails& details) {
@@ -258,6 +297,7 @@
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(FileSelectObserver, message)
     IPC_MESSAGE_HANDLER(ViewHostMsg_RunFileChooser, OnRunFileChooser)
+    IPC_MESSAGE_HANDLER(ViewHostMsg_EnumerateDirectory, OnEnumerateDirectory)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
 
@@ -271,3 +311,20 @@
   file_select_helper_->RunFileChooser(tab_contents()->render_view_host(),
                                       params);
 }
+
+void FileSelectObserver::OnEnumerateDirectory(int request_id,
+                                              const FilePath& path) {
+  ChildProcessSecurityPolicy* policy =
+      ChildProcessSecurityPolicy::GetInstance();
+  if (!policy->CanReadDirectory(
+          tab_contents()->render_view_host()->process()->id(),
+          path)) {
+    return;
+  }
+
+  if (!file_select_helper_.get())
+    file_select_helper_.reset(new FileSelectHelper(tab_contents()->profile()));
+  file_select_helper_->EnumerateDirectory(request_id,
+                                          tab_contents()->render_view_host(),
+                                          path);
+}