Input file type now supported in extension popups.
To do this, this CL generalize the TabContentsFileSelectHelper (renamed FileSelectHelper) so it is associated with a RenderViewHost rather than a TabContents.
This allows the extension popups which don't use a TabContents to use it.

As part of that, I also moved GetTopLevelNativeWindow() from TabContentsView to TabContent, as it can be implemented in a non-platform specific way.

BUG=28829
TEST=Make sure you can still open file on web pages (such as http://www.cs.tut.fi/~jkorpela/forms/file.html.
     Create an extension with a popup that contains an input file tag. Make sure it does open a file dialog and lets you choose a file.

Review URL: http://codereview.chromium.org/3209002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@59105 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc
index ba74da88..13619ff 100644
--- a/chrome/browser/extensions/extension_host.cc
+++ b/chrome/browser/extensions/extension_host.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/extensions/extension_message_service.h"
 #include "chrome/browser/extensions/extension_tabs_module.h"
 #include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/file_select_helper.h"
 #include "chrome/browser/in_process_webkit/dom_storage_context.h"
 #include "chrome/browser/in_process_webkit/webkit_context.h"
 #include "chrome/browser/message_box_handler.h"
@@ -677,6 +678,12 @@
   }
 }
 
+RenderViewHostDelegate::FileSelect* ExtensionHost::GetFileSelectDelegate() {
+  if (file_select_helper_.get() == NULL)
+    file_select_helper_.reset(new FileSelectHelper(profile()));
+  return file_select_helper_.get();
+}
+
 int ExtensionHost::GetBrowserWindowID() const {
   // Hosts not attached to any browser window have an id of -1.  This includes
   // those mentioned below, and background pages.
diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h
index 0148dfaf..80dbf14 100644
--- a/chrome/browser/extensions/extension_host.h
+++ b/chrome/browser/extensions/extension_host.h
@@ -26,6 +26,7 @@
 
 class Browser;
 class Extension;
+class FileSelectHelper;
 class RenderProcessHost;
 class RenderWidgetHostView;
 class TabContents;
@@ -115,6 +116,7 @@
   virtual const GURL& GetURL() const { return url_; }
   virtual void RenderViewCreated(RenderViewHost* render_view_host);
   virtual ViewType::Type GetRenderViewType() const;
+  virtual FileSelect* GetFileSelectDelegate();
   virtual int GetBrowserWindowID() const;
   virtual void RenderViewGone(RenderViewHost* render_view_host);
   virtual void DidNavigate(RenderViewHost* render_view_host,
@@ -268,6 +270,9 @@
   // Used to measure how long it's been since the host was created.
   PerfTimer since_created_;
 
+  // FileSelectHelper, lazily created.
+  scoped_ptr<FileSelectHelper> file_select_helper_;
+
   DISALLOW_COPY_AND_ASSIGN(ExtensionHost);
 };
 
diff --git a/chrome/browser/file_select_helper.cc b/chrome/browser/file_select_helper.cc
new file mode 100644
index 0000000..6ee0b737
--- /dev/null
+++ b/chrome/browser/file_select_helper.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/file_select_helper.h"
+
+#include "app/l10n_util.h"
+#include "base/file_util.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/mime_util.h"
+#include "chrome/browser/platform_util.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/shell_dialogs.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_view.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/render_messages_params.h"
+#include "grit/generated_resources.h"
+
+FileSelectHelper::FileSelectHelper(Profile* profile)
+    : profile_(profile),
+      render_view_host_(NULL),
+      select_file_dialog_(),
+      dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
+}
+
+FileSelectHelper::~FileSelectHelper() {
+  // There may be pending file dialogs, we need to tell them that we've gone
+  // away so they don't try and call back to us.
+  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();
+  }
+}
+
+void FileSelectHelper::FileSelected(const FilePath& path,
+                                    int index, void* params) {
+  if (!render_view_host_)
+    return;
+
+  profile_->set_last_selected_directory(path.DirName());
+
+  if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
+    DirectorySelected(path);
+    return;
+  }
+
+  std::vector<FilePath> files;
+  files.push_back(path);
+  render_view_host_->FilesSelectedInChooser(files);
+  // We are done with this showing of the dialog.
+  render_view_host_ = NULL;
+}
+
+void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
+                                          void* params) {
+  if (!files.empty())
+    profile_->set_last_selected_directory(files[0].DirName());
+  if (!render_view_host_)
+    return;
+
+  render_view_host_->FilesSelectedInChooser(files);
+  // We are done with this showing of the dialog.
+  render_view_host_ = NULL;
+}
+
+void FileSelectHelper::FileSelectionCanceled(void* params) {
+  if (!render_view_host_)
+    return;
+
+  // If the user cancels choosing a file to upload we pass back an
+  // empty vector.
+  render_view_host_->FilesSelectedInChooser(std::vector<FilePath>());
+
+  // We are done with this showing of the dialog.
+  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::OnListFile(
+    const net::DirectoryLister::DirectoryListerData& data) {
+  // Directory upload only cares about files.  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))
+    return;
+
+  directory_lister_results_.push_back(data.path);
+}
+
+void FileSelectHelper::OnListDone(int error) {
+  if (!render_view_host_)
+    return;
+
+  if (error) {
+    FileSelectionCanceled(NULL);
+    return;
+  }
+
+  render_view_host_->FilesSelectedInChooser(directory_lister_results_);
+  render_view_host_ = NULL;
+  directory_lister_ = NULL;
+  directory_lister_results_.clear();
+}
+
+SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
+    const string16& accept_types) {
+  if (accept_types.empty())
+    return NULL;
+
+  // Split the accept-type string on commas.
+  std::vector<string16> mime_types;
+  base::SplitStringUsingSubstr(accept_types, ASCIIToUTF16(","), &mime_types);
+  if (mime_types.empty())
+    return NULL;
+
+  // Create FileTypeInfo and pre-allocate for the first extension list.
+  scoped_ptr<SelectFileDialog::FileTypeInfo> file_type(
+      new SelectFileDialog::FileTypeInfo());
+  file_type->include_all_files = true;
+  file_type->extensions.resize(1);
+  std::vector<FilePath::StringType>* extensions = &file_type->extensions.back();
+
+  // Find the correspondinge extensions.
+  int valid_type_count = 0;
+  int description_id = 0;
+  for (size_t i = 0; i < mime_types.size(); ++i) {
+    string16 mime_type = mime_types[i];
+    std::string ascii_mime_type = StringToLowerASCII(UTF16ToASCII(mime_type));
+
+    TrimWhitespace(ascii_mime_type, TRIM_ALL, &ascii_mime_type);
+    if (ascii_mime_type.empty())
+      continue;
+
+    size_t old_extension_size = extensions->size();
+    if (ascii_mime_type == "image/*") {
+      description_id = IDS_IMAGE_FILES;
+      net::GetImageExtensions(extensions);
+    } else if (ascii_mime_type == "audio/*") {
+      description_id = IDS_AUDIO_FILES;
+      net::GetAudioExtensions(extensions);
+    } else if (ascii_mime_type == "video/*") {
+      description_id = IDS_VIDEO_FILES;
+      net::GetVideoExtensions(extensions);
+    } else {
+      net::GetExtensionsForMimeType(ascii_mime_type, extensions);
+    }
+
+    if (extensions->size() > old_extension_size)
+      valid_type_count++;
+  }
+
+  // Use a generic description "Custom Files" if either of the following is
+  // true:
+  // 1) There're multiple types specified, like "audio/*,video/*"
+  // 2) There're multiple extensions for a MIME type without parameter, like
+  //    "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
+  //    dialog uses the first extension in the list to form the description,
+  //    like "EHTML Files". This is not what we want.
+  if (valid_type_count > 1 ||
+      (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
+    description_id = IDS_CUSTOM_FILES;
+
+  if (description_id) {
+    file_type->extension_description_overrides.push_back(
+        l10n_util::GetStringUTF16(description_id));
+  }
+
+  return file_type.release();
+}
+
+void FileSelectHelper::RunFileChooser(
+    RenderViewHost* render_view_host,
+    const ViewHostMsg_RunFileChooser_Params &params) {
+  DCHECK(!render_view_host_);
+  render_view_host_ = render_view_host;
+  notification_registrar_.RemoveAll();
+  notification_registrar_.Add(this,
+                              NotificationType::RENDER_WIDGET_HOST_DESTROYED,
+                              Source<RenderViewHost>(render_view_host));
+
+  if (!select_file_dialog_.get())
+    select_file_dialog_ = SelectFileDialog::Create(this);
+
+  switch (params.mode) {
+    case ViewHostMsg_RunFileChooser_Params::Open:
+      dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
+      break;
+    case ViewHostMsg_RunFileChooser_Params::OpenMultiple:
+      dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
+      break;
+    case ViewHostMsg_RunFileChooser_Params::OpenFolder:
+      dialog_type_ = SelectFileDialog::SELECT_FOLDER;
+      break;
+    case ViewHostMsg_RunFileChooser_Params::Save:
+      dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
+      break;
+    default:
+      dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;  // Prevent warning.
+      NOTREACHED();
+  }
+  scoped_ptr<SelectFileDialog::FileTypeInfo> file_types(
+      GetFileTypesFromAcceptType(params.accept_types));
+  FilePath default_file_name = params.default_file_name;
+  if (default_file_name.empty())
+    default_file_name = profile_->last_selected_directory();
+
+  gfx::NativeWindow owning_window =
+      platform_util::GetTopLevel(render_view_host_->view()->GetNativeView());
+  select_file_dialog_->SelectFile(dialog_type_,
+                                  params.title,
+                                  default_file_name,
+                                  file_types.get(),
+                                  0,
+                                  FILE_PATH_LITERAL(""),
+                                  owning_window,
+                                  NULL);
+}
+
+void FileSelectHelper::Observe(NotificationType type,
+                               const NotificationSource& source,
+                               const NotificationDetails& details) {
+  DCHECK(type == NotificationType::RENDER_WIDGET_HOST_DESTROYED);
+  DCHECK(Details<RenderViewHost>(details).ptr() == render_view_host_);
+  render_view_host_ = NULL;
+}
diff --git a/chrome/browser/tab_contents/tab_contents_file_select_helper.h b/chrome/browser/file_select_helper.h
similarity index 63%
rename from chrome/browser/tab_contents/tab_contents_file_select_helper.h
rename to chrome/browser/file_select_helper.h
index e8e9b43..ba8441b 100644
--- a/chrome/browser/tab_contents/tab_contents_file_select_helper.h
+++ b/chrome/browser/file_select_helper.h
@@ -2,28 +2,30 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_TAB_CONTENTS_TAB_CONTENTS_FILE_SELECT_HELPER_H_
-#define CHROME_BROWSER_TAB_CONTENTS_TAB_CONTENTS_FILE_SELECT_HELPER_H_
+#ifndef CHROME_BROWSER_FILE_SELECT_HELPER_H_
+#define CHROME_BROWSER_FILE_SELECT_HELPER_H_
 #pragma once
 
 #include <vector>
 
 #include "chrome/browser/shell_dialogs.h"
 #include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
 #include "net/base/directory_lister.h"
 
+class Profile;
 class RenderViewHost;
-class TabContents;
 struct ViewHostMsg_RunFileChooser_Params;
 
-class TabContentsFileSelectHelper
+class FileSelectHelper
     : public SelectFileDialog::Listener,
       public net::DirectoryLister::DirectoryListerDelegate,
-      public RenderViewHostDelegate::FileSelect {
+      public RenderViewHostDelegate::FileSelect,
+      public NotificationObserver {
  public:
-  explicit TabContentsFileSelectHelper(TabContents* tab_contents);
-
-  ~TabContentsFileSelectHelper();
+  explicit FileSelectHelper(Profile* profile);
+  ~FileSelectHelper();
 
   // SelectFileDialog::Listener
   virtual void FileSelected(const FilePath& path, int index, void* params);
@@ -37,11 +39,14 @@
   virtual void OnListDone(int error);
 
   // RenderViewHostDelegate::FileSelect
-  virtual void RunFileChooser(const ViewHostMsg_RunFileChooser_Params& params);
+  virtual void RunFileChooser(RenderViewHost* render_view_host,
+                              const ViewHostMsg_RunFileChooser_Params& params);
 
  private:
-  // Returns the RenderViewHost of tab_contents_.
-  RenderViewHost* GetRenderViewHost();
+  // NotificationObserver implementation.
+  virtual void Observe(NotificationType type,
+                       const NotificationSource& source,
+                       const NotificationDetails& details);
 
   // Helper method for handling the SelectFileDialog::Listener callbacks.
   void DirectorySelected(const FilePath& path);
@@ -52,9 +57,11 @@
   SelectFileDialog::FileTypeInfo* GetFileTypesFromAcceptType(
       const string16& accept_types);
 
-  // The tab contents this class is helping.  |tab_contents_| owns this object,
-  // so this pointer is guaranteed to be valid.
-  TabContents* tab_contents_;
+  // Profile used to set/retrieve the last used directory.
+  Profile* profile_;
+
+  // The RenderViewHost for the page we are associated with.
+  RenderViewHost* render_view_host_;
 
   // Dialog box used for choosing files to upload from file form fields.
   scoped_refptr<SelectFileDialog> select_file_dialog_;
@@ -69,7 +76,10 @@
   // as the listing proceeds.
   std::vector<FilePath> directory_lister_results_;
 
-  DISALLOW_COPY_AND_ASSIGN(TabContentsFileSelectHelper);
+  // Registrar for notifications regarding our RenderViewHost.
+  NotificationRegistrar notification_registrar_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileSelectHelper);
 };
 
-#endif  // CHROME_BROWSER_TAB_CONTENTS_TAB_CONTENTS_FILE_SELECT_HELPER_H_
+#endif  // CHROME_BROWSER_FILE_SELECT_HELPER_H_
diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc
index 4152008..9d06007 100644
--- a/chrome/browser/renderer_host/render_view_host.cc
+++ b/chrome/browser/renderer_host/render_view_host.cc
@@ -1383,7 +1383,7 @@
   RenderViewHostDelegate::FileSelect* file_select_delegate =
       delegate()->GetFileSelectDelegate();
   if (file_select_delegate)
-    file_select_delegate->RunFileChooser(params);
+    file_select_delegate->RunFileChooser(this, params);
 }
 
 void RenderViewHost::OnMsgRunJavaScriptMessage(
diff --git a/chrome/browser/renderer_host/render_view_host_delegate.h b/chrome/browser/renderer_host/render_view_host_delegate.h
index dd414b9..27f9dc8 100644
--- a/chrome/browser/renderer_host/render_view_host_delegate.h
+++ b/chrome/browser/renderer_host/render_view_host_delegate.h
@@ -605,6 +605,7 @@
    public:
     // A file chooser should be shown.
     virtual void RunFileChooser(
+        RenderViewHost* render_view_host,
         const ViewHostMsg_RunFileChooser_Params& params) = 0;
 
    protected:
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index 4a1511ed..3c35e61 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -45,6 +45,7 @@
 #include "chrome/browser/history/history_types.h"
 #include "chrome/browser/history/top_sites.h"
 #include "chrome/browser/favicon_service.h"
+#include "chrome/browser/file_select_helper.h"
 #include "chrome/browser/find_bar_state.h"
 #include "chrome/browser/google/google_util.h"
 #include "chrome/browser/host_content_settings_map.h"
@@ -79,7 +80,6 @@
 #include "chrome/browser/tab_contents/navigation_entry.h"
 #include "chrome/browser/tab_contents/provisional_load_details.h"
 #include "chrome/browser/tab_contents/tab_contents_delegate.h"
-#include "chrome/browser/tab_contents/tab_contents_file_select_helper.h"
 #include "chrome/browser/tab_contents/tab_contents_ssl_helper.h"
 #include "chrome/browser/tab_contents/tab_contents_view.h"
 #include "chrome/browser/tab_contents/thumbnail_generator.h"
@@ -2297,7 +2297,7 @@
 
 RenderViewHostDelegate::FileSelect* TabContents::GetFileSelectDelegate() {
   if (file_select_helper_.get() == NULL)
-    file_select_helper_.reset(new TabContentsFileSelectHelper(this));
+    file_select_helper_.reset(new FileSelectHelper(profile()));
   return file_select_helper_.get();
 }
 
diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h
index 0dd05cf..94b2ca7e 100644
--- a/chrome/browser/tab_contents/tab_contents.h
+++ b/chrome/browser/tab_contents/tab_contents.h
@@ -69,6 +69,7 @@
 class DOMUI;
 class DownloadItem;
 class Extension;
+class FileSelectHelper;
 class InfoBarDelegate;
 class LoadNotificationDetails;
 class MatchPreview;
@@ -82,7 +83,6 @@
 class SkBitmap;
 class TabContents;
 class TabContentsDelegate;
-class TabContentsFileSelectHelper;
 class TabContentsSSLHelper;
 class TabContentsView;
 class URLPattern;
@@ -1074,8 +1074,8 @@
   // BlockedPluginManager, lazily created.
   scoped_ptr<BlockedPluginManager> blocked_plugin_manager_;
 
-  // TabContentsFileSelectHelper, lazily created.
-  scoped_ptr<TabContentsFileSelectHelper> file_select_helper_;
+  // FileSelectHelper, lazily created.
+  scoped_ptr<FileSelectHelper> file_select_helper_;
 
   // Handles drag and drop event forwarding to extensions.
   BookmarkDrag* bookmark_drag_;
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 195316a5..07da92f 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1513,6 +1513,8 @@
         'browser/file_path_watcher_inotify.cc',
         'browser/file_path_watcher_mac.cc',
         'browser/file_path_watcher_win.cc',
+        'browser/file_select_helper.cc',
+        'browser/file_select_helper.h',
         'browser/file_system/file_system_dispatcher_host.cc',
         'browser/file_system/file_system_dispatcher_host.h',
         'browser/file_system/file_system_host_context.cc',
@@ -2680,8 +2682,6 @@
         'browser/tab_contents/tab_contents.h',
         'browser/tab_contents/tab_contents_delegate.cc',
         'browser/tab_contents/tab_contents_delegate.h',
-        'browser/tab_contents/tab_contents_file_select_helper.cc',
-        'browser/tab_contents/tab_contents_file_select_helper.h',
         'browser/tab_contents/tab_contents_ssl_helper.cc',
         'browser/tab_contents/tab_contents_ssl_helper.h',
         'browser/tab_contents/tab_contents_view.cc',