Fix crash when running the file chooser dialog from PPAPI.BUG=97637TEST=manual

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@103325 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/file_select_helper.cc b/chrome/browser/file_select_helper.cc
index 391d1318..f656e10a 100644
--- a/chrome/browser/file_select_helper.cc
+++ b/chrome/browser/file_select_helper.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "base/bind.h"
 #include "base/file_util.h"
 #include "base/string_split.h"
 #include "base/string_util.h"
@@ -45,7 +46,9 @@
 FileSelectHelper::FileSelectHelper(Profile* profile)
     : profile_(profile),
       render_view_host_(NULL),
+      tab_contents_(NULL),
       select_file_dialog_(),
+      select_file_types_(),
       dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
 }
 
@@ -81,10 +84,9 @@
   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;
+
   // No members should be accessed from here on.
-  delete this;
+  RunFileChooserEnd();
 }
 
 void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
@@ -95,10 +97,9 @@
     return;
 
   render_view_host_->FilesSelectedInChooser(files);
-  // We are done with this showing of the dialog.
-  render_view_host_ = NULL;
+
   // No members should be accessed from here on.
-  delete this;
+  RunFileChooserEnd();
 }
 
 void FileSelectHelper::FileSelectionCanceled(void* params) {
@@ -109,10 +110,8 @@
   // empty vector.
   render_view_host_->FilesSelectedInChooser(std::vector<FilePath>());
 
-  // We are done with this showing of the dialog.
-  render_view_host_ = NULL;
   // No members should be accessed from here on.
-  delete this;
+  RunFileChooserEnd();
 }
 
 void FileSelectHelper::StartNewEnumeration(const FilePath& path,
@@ -164,8 +163,8 @@
     entry->rvh_->FilesSelectedInChooser(entry->results_);
   else
     entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
-  // No members should be accessed from here on.
-  delete this;
+
+  EnumerateDirectoryEnd();
 }
 
 SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
@@ -243,11 +242,43 @@
     TabContents* tab_contents,
     const ViewHostMsg_RunFileChooser_Params& params) {
   DCHECK(!render_view_host_);
+  DCHECK(!tab_contents_);
   render_view_host_ = render_view_host;
+  tab_contents_ = tab_contents;
   notification_registrar_.RemoveAll();
   notification_registrar_.Add(
       this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
-      Source<RenderWidgetHost>(render_view_host));
+      Source<RenderWidgetHost>(render_view_host_));
+  notification_registrar_.Add(
+      this, content::NOTIFICATION_TAB_CONTENTS_DESTROYED,
+      Source<TabContents>(tab_contents_));
+
+  BrowserThread::PostTask(
+      BrowserThread::FILE, FROM_HERE,
+      base::Bind(&FileSelectHelper::RunFileChooserOnFileThread, this, params));
+
+  // Because this class returns notifications to the RenderViewHost, 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, until the last callback is received from the file dialog.
+  // At that point, we must call RunFileChooserEnd().
+  AddRef();
+}
+
+void FileSelectHelper::RunFileChooserOnFileThread(
+    const ViewHostMsg_RunFileChooser_Params& params) {
+  select_file_types_.reset(
+      GetFileTypesFromAcceptType(params.accept_types));
+
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::Bind(&FileSelectHelper::RunFileChooserOnUIThread, this, params));
+}
+
+void FileSelectHelper::RunFileChooserOnUIThread(
+    const ViewHostMsg_RunFileChooser_Params& params) {
+  if (!render_view_host_ || !tab_contents_)
+    return;
 
   if (!select_file_dialog_.get())
     select_file_dialog_ = SelectFileDialog::Create(this);
@@ -269,8 +300,6 @@
       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();
@@ -278,29 +307,68 @@
   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(),
-                                  file_types.get() ? 1 : 0,  // 1-based index.
-                                  FILE_PATH_LITERAL(""),
-                                  tab_contents,
-                                  owning_window,
-                                  NULL);
+  select_file_dialog_->SelectFile(
+      dialog_type_,
+      params.title,
+      default_file_name,
+      select_file_types_.get(),
+      select_file_types_.get() ? 1 : 0,  // 1-based index.
+      FILE_PATH_LITERAL(""),
+      tab_contents_,
+      owning_window,
+      NULL);
+
+  select_file_types_.reset();
+}
+
+// This method is called when we receive the last callback from the file
+// chooser dialog. Perform any cleanup and release the reference we added
+// in RunFileChooser().
+void FileSelectHelper::RunFileChooserEnd() {
+  render_view_host_ = NULL;
+  tab_contents_ = NULL;
+  Release();
 }
 
 void FileSelectHelper::EnumerateDirectory(int request_id,
                                           RenderViewHost* render_view_host,
                                           const FilePath& path) {
   DCHECK_NE(kFileSelectEnumerationId, request_id);
+
+  // Because this class returns notifications to the RenderViewHost, 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, until the last callback is received from the enumeration
+  // code. At that point, we must call EnumerateDirectoryEnd().
+  AddRef();
   StartNewEnumeration(path, request_id, render_view_host);
 }
 
+// This method is called when we receive the last callback from the enumeration
+// code. Perform any cleanup and release the reference we added in
+// EnumerateDirectory().
+void FileSelectHelper::EnumerateDirectoryEnd() {
+  Release();
+}
+
 void FileSelectHelper::Observe(int type,
                                const NotificationSource& source,
                                const NotificationDetails& details) {
-  DCHECK(type == content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED);
-  DCHECK(Source<RenderWidgetHost>(source).ptr() == render_view_host_);
-  render_view_host_ = NULL;
+  switch (type) {
+    case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
+      DCHECK(Source<RenderWidgetHost>(source).ptr() == render_view_host_);
+      render_view_host_ = NULL;
+      break;
+    }
+
+    case content::NOTIFICATION_TAB_CONTENTS_DESTROYED: {
+      DCHECK(Source<TabContents>(source).ptr() == tab_contents_);
+      tab_contents_ = NULL;
+      break;
+    }
+
+    default:
+      NOTREACHED();
+  }
 }