FileChooser: Mojoify FileChooser IPC, part 4

Introduce content::FileSelectListener, which receives the result of FileSelectHelper.
An implementation class of FileSelectListener will implement blink.mojom.FileChooser
mojo interface in a following CL.

a) Add content::FileSelectListener
 - content/public/browser/BUILD.gn
 - content/public/browser/file_select_listener.h

b) Receive FileSelectHelper arguments, and call its methods to notify results
  instead of RFH::FilesSelectedInChooser() and RVH::
  DirectoryEnumerationFinished().
 - chrome/browser/file_select_helper.cc
 - chrome/browser/file_select_helper.h

c) Add FileChooserImpl class implementing content::FileSelectListener. A
  FileChooserImpl instance is passed to RenderFrameHostDelgate::
  RunFileChooser().  RFH::FilesSelectedInChooser() is removed because
  FileChooserImpl receives a file list, and it sends the list via IPC.
 - content/public/browser/render_frame_host.h
 - content/browser/frame_host/render_frame_host_impl.cc
 - content/browser/frame_host/render_frame_host_impl.h

d) Add ViewFileChooser class implementing content::FileSelectListener. A
  ViewFileChooser instance is passed to WebContentsDelegate::
  EnumerateDirectory(). RVH::DirectoryEnumerationFinished() is called by
  ViewFileChooser instead of FileSelectHelper.
 - content/browser/web_contents/web_contents_impl.cc
 - content/browser/web_contents/web_contents_impl.h

e) Add FileSelectListener argument, and call FileSelectListener::FileSelected()
  instead of RFH::FilesSelectedInChooser().  Maintain a FileSelectListener instance
  in AwWebContentsDelegate class.
 - android_webview/browser/aw_web_contents_delegate.cc
 - android_webview/browser/aw_web_contents_delegate.h


f) Add FileSelectListener argument, and forward it to another function or call
  FileSelectListener::FileSelectionCanceled()
 - chrome/browser/android/tab_web_contents_delegate_android.cc
 - chrome/browser/android/tab_web_contents_delegate_android.h
 - chrome/browser/devtools/devtools_window.cc
 - chrome/browser/devtools/devtools_window.h
 - chrome/browser/extensions/extension_view_host.cc
 - chrome/browser/extensions/extension_view_host.h
 - chrome/browser/ui/apps/chrome_app_delegate.cc
 - chrome/browser/ui/apps/chrome_app_delegate.h
 - chrome/browser/ui/browser.cc
 - chrome/browser/ui/browser.h
 - components/guest_view/browser/guest_view_base.cc
 - components/guest_view/browser/guest_view_base.h
 - content/browser/frame_host/render_frame_host_delegate.cc
 - content/browser/frame_host/render_frame_host_delegate.h
 - content/public/browser/web_contents_delegate.cc
 - content/public/browser/web_contents_delegate.h
 - extensions/browser/app_window/app_delegate.h
 - extensions/browser/app_window/app_window.cc
 - extensions/browser/app_window/app_window.h
 - extensions/shell/browser/shell_app_delegate.cc
 - extensions/shell/browser/shell_app_delegate.h

g) Add FileSelectListener argument, and call FileSelectListener::FileSelected()
  instead of RFH::FilesSelectedInChooser()
 - chrome/browser/ssl/security_state_tab_helper_browsertest.cc
 - content/test/content_browser_test_utils_internal.cc
 - content/test/content_browser_test_utils_internal.h

h) Add MockFileLister and pass it to RunFileChooser() to avoid null dereference.
 - content/browser/web_contents/web_contents_impl_browsertest.cc


FYI: All-in-one CL: https://chromium-review.googlesource.com/1170454

Bug: 869257
Change-Id: I190159bd5819d228c703028584b9929aa2ad80c2
Reviewed-on: https://chromium-review.googlesource.com/c/1251182
Reviewed-by: Istiaque Ahmed <[email protected]>
Reviewed-by: Richard Coles <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Commit-Queue: Kent Tamura <[email protected]>
Cr-Commit-Position: refs/heads/master@{#596451}
diff --git a/content/browser/frame_host/render_frame_host_delegate.cc b/content/browser/frame_host/render_frame_host_delegate.cc
index 623cd42..b77280e 100644
--- a/content/browser/frame_host/render_frame_host_delegate.cc
+++ b/content/browser/frame_host/render_frame_host_delegate.cc
@@ -8,6 +8,7 @@
 #include "base/strings/string16.h"
 #include "build/build_config.h"
 #include "content/browser/frame_host/render_frame_host_delegate.h"
+#include "content/public/browser/file_select_listener.h"
 #include "ipc/ipc_message.h"
 #include "ui/gfx/native_widget_types.h"
 #include "url/gurl.h"
@@ -33,6 +34,13 @@
   return false;
 }
 
+void RenderFrameHostDelegate::RunFileChooser(
+    RenderFrameHost* render_frame_host,
+    std::unique_ptr<FileSelectListener> listener,
+    const blink::mojom::FileChooserParams& params) {
+  listener->FileSelectionCanceled();
+}
+
 WebContents* RenderFrameHostDelegate::GetAsWebContents() {
   return nullptr;
 }
diff --git a/content/browser/frame_host/render_frame_host_delegate.h b/content/browser/frame_host/render_frame_host_delegate.h
index d1b04fb..61b1c2956 100644
--- a/content/browser/frame_host/render_frame_host_delegate.h
+++ b/content/browser/frame_host/render_frame_host_delegate.h
@@ -62,6 +62,7 @@
 }
 
 namespace content {
+class FileSelectListener;
 class FrameTreeNode;
 class InterstitialPage;
 class PageState;
@@ -137,8 +138,12 @@
                                       IPC::Message* reply_msg) {}
 
   // Called when a file selection is to be done.
-  virtual void RunFileChooser(RenderFrameHost* render_frame_host,
-                              const blink::mojom::FileChooserParams& params) {}
+  // Overrides of this function must call either listener->FileSelected() or
+  // listener->FileSelectionCanceled().
+  virtual void RunFileChooser(
+      RenderFrameHost* render_frame_host,
+      std::unique_ptr<content::FileSelectListener> listener,
+      const blink::mojom::FileChooserParams& params);
 
   // The pending page load was canceled, so the address bar should be updated.
   virtual void DidCancelLoading() {}
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 5bdaa0f1..8a746e44 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -120,6 +120,7 @@
 #include "content/public/browser/browser_plugin_guest_manager.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_widget_host_view.h"
@@ -503,6 +504,99 @@
       blob_url_loader_factory(std::move(blob_url_loader_factory)),
       navigation_client(std::move(navigation_client)) {}
 
+class FileChooserImpl : public content::FileSelectListener,
+                        private content::WebContentsObserver {
+ public:
+  FileChooserImpl(RenderFrameHostImpl* render_frame_host)
+      : render_frame_host_(render_frame_host) {
+    Observe(WebContents::FromRenderFrameHost(render_frame_host));
+  }
+
+  ~FileChooserImpl() override {
+#if DCHECK_IS_ON()
+    DCHECK(was_file_select_listener_function_called_)
+        << "Should call either FileSelectListener::FileSelected() or "
+           "FileSelectListener::FileSelectionCanceled()";
+#endif
+  }
+
+  // FileSelectListener overrides:
+
+  void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files,
+                    blink::mojom::FileChooserParams::Mode mode) override {
+#if DCHECK_IS_ON()
+    DCHECK(!was_file_select_listener_function_called_)
+        << "Should not call both of FileSelectListener::FileSelected() and "
+           "FileSelectListener::FileSelectionCanceled()";
+    was_file_select_listener_function_called_ = true;
+#endif
+    if (!render_frame_host_)
+      return;
+    storage::FileSystemContext* file_system_context = nullptr;
+    const int pid = render_frame_host_->GetProcess()->GetID();
+    auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+    // Grant the security access requested to the given files.
+    for (const auto& file : files) {
+      if (mode == blink::mojom::FileChooserParams::Mode::kSave) {
+        policy->GrantCreateReadWriteFile(pid,
+                                         file->get_native_file()->file_path);
+      } else {
+        if (file->is_file_system()) {
+          if (!file_system_context) {
+            file_system_context =
+                BrowserContext::GetStoragePartition(
+                    render_frame_host_->GetProcess()->GetBrowserContext(),
+                    render_frame_host_->GetSiteInstance())
+                    ->GetFileSystemContext();
+          }
+          policy->GrantReadFileSystem(
+              pid, file_system_context->CrackURL(file->get_file_system()->url)
+                       .mount_filesystem_id());
+        } else {
+          policy->GrantReadFile(pid, file->get_native_file()->file_path);
+        }
+      }
+    }
+    render_frame_host_->Send(new FrameMsg_RunFileChooserResponse(
+        render_frame_host_->routing_id(), files));
+  }
+
+  void FileSelectionCanceled() override {
+#if DCHECK_IS_ON()
+    DCHECK(!was_file_select_listener_function_called_)
+        << "Should not call both of FileSelectListener::FileSelected() and "
+           "FileSelectListener::FileSelectionCanceled()";
+    was_file_select_listener_function_called_ = true;
+#endif
+    if (!render_frame_host_)
+      return;
+    render_frame_host_->Send(new FrameMsg_RunFileChooserResponse(
+        render_frame_host_->routing_id(),
+        std::vector<blink::mojom::FileChooserFileInfoPtr>()));
+  }
+
+ private:
+  // content::WebContentsObserver overrides:
+
+  void RenderFrameHostChanged(RenderFrameHost* old_host,
+                              RenderFrameHost* new_host) override {
+    if (old_host == render_frame_host_)
+      render_frame_host_ = nullptr;
+  }
+
+  void RenderFrameDeleted(RenderFrameHost* render_frame_host) override {
+    if (render_frame_host == render_frame_host_)
+      render_frame_host_ = nullptr;
+  }
+
+  void WebContentsDestroyed() override { render_frame_host_ = nullptr; }
+
+  RenderFrameHostImpl* render_frame_host_;
+#if DCHECK_IS_ON()
+  bool was_file_select_listener_function_called_ = false;
+#endif
+};
+
 // static
 RenderFrameHost* RenderFrameHost::FromID(int render_process_id,
                                          int render_frame_id) {
@@ -2394,16 +2488,18 @@
 
 void RenderFrameHostImpl::OnRunFileChooser(
     const blink::mojom::FileChooserParams& params) {
+  auto listener = std::make_unique<FileChooserImpl>(this);
   // Do not allow messages with absolute paths in them as this can permit a
   // renderer to coerce the browser to perform I/O on a renderer controlled
   // path.
   if (params.default_file_name != params.default_file_name.BaseName()) {
     bad_message::ReceivedBadMessage(GetProcess(),
                                     bad_message::RFH_FILE_CHOOSER_PATH);
+    listener->FileSelectionCanceled();
     return;
   }
 
-  delegate_->RunFileChooser(this, params);
+  delegate_->RunFileChooser(this, std::move(listener), params);
 }
 
 void RenderFrameHostImpl::RequestTextSurroundingSelection(
@@ -4704,34 +4800,6 @@
   return frame_tree_node_->render_manager()->GetProxyCount();
 }
 
-void RenderFrameHostImpl::FilesSelectedInChooser(
-    const std::vector<blink::mojom::FileChooserFileInfoPtr>& files,
-    blink::mojom::FileChooserParams::Mode permissions) {
-  storage::FileSystemContext* const file_system_context =
-      BrowserContext::GetStoragePartition(GetProcess()->GetBrowserContext(),
-                                          GetSiteInstance())
-          ->GetFileSystemContext();
-  // Grant the security access requested to the given files.
-  for (const auto& file : files) {
-    if (permissions == blink::mojom::FileChooserParams::Mode::kSave) {
-      ChildProcessSecurityPolicyImpl::GetInstance()->GrantCreateReadWriteFile(
-          GetProcess()->GetID(), file->get_native_file()->file_path);
-    } else {
-      if (file->is_file_system()) {
-        ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFileSystem(
-            GetProcess()->GetID(),
-            file_system_context->CrackURL(file->get_file_system()->url)
-                .mount_filesystem_id());
-      } else {
-        ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
-            GetProcess()->GetID(), file->get_native_file()->file_path);
-      }
-    }
-  }
-
-  Send(new FrameMsg_RunFileChooserResponse(routing_id_, files));
-}
-
 bool RenderFrameHostImpl::HasSelection() {
   return has_selection_;
 }
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index fe23b03..a03c4c1c 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -231,9 +231,6 @@
   bool IsRenderFrameLive() override;
   bool IsCurrent() override;
   int GetProxyCount() override;
-  void FilesSelectedInChooser(
-      const std::vector<blink::mojom::FileChooserFileInfoPtr>& files,
-      blink::mojom::FileChooserParams::Mode permissions) override;
   bool HasSelection() override;
   void RequestTextSurroundingSelection(
       const TextSurroundingSelectionCallback& callback,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 169b1d8..9cb87f79 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -106,6 +106,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/download_manager.h"
+#include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/focused_node_details.h"
 #include "content/public/browser/guest_mode.h"
 #include "content/public/browser/invalidate_type.h"
@@ -298,6 +299,46 @@
   return a->frame_tree_node()->depth() < b->frame_tree_node()->depth();
 }
 
+// TODO(tkent): This will be merged into FileChooserImpl in
+// render_frame_host_impl.cc.
+class ViewFileSelectListener : public FileSelectListener,
+                               private WebContentsObserver {
+ public:
+  ViewFileSelectListener(WebContents* web_contents, int request_id)
+      : web_contents_(web_contents), request_id_(request_id) {
+    Observe(web_contents);
+  }
+  ~ViewFileSelectListener() override = default;
+
+ private:
+  // content::FileSelectListener overrides:
+
+  void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files,
+                    blink::mojom::FileChooserParams::Mode mode) override {
+    if (!web_contents_)
+      return;
+    std::vector<base::FilePath> file_path_list;
+    for (const auto& file_info : files) {
+      file_path_list.push_back(file_info->get_native_file()->file_path);
+    }
+    web_contents_->GetRenderViewHost()->DirectoryEnumerationFinished(
+        request_id_, file_path_list);
+  }
+
+  void FileSelectionCanceled() override {
+    if (!web_contents_)
+      return;
+    web_contents_->GetRenderViewHost()->DirectoryEnumerationFinished(
+        request_id_, std::vector<base::FilePath>());
+  }
+
+  // content::WebContentsObserver override:
+  void WebContentsDestroyed() override { web_contents_ = nullptr; }
+
+  WebContents* web_contents_;
+  int request_id_;
+};
+
 }  // namespace
 
 std::unique_ptr<WebContents> WebContents::Create(
@@ -4589,10 +4630,11 @@
 
   ChildProcessSecurityPolicyImpl* policy =
       ChildProcessSecurityPolicyImpl::GetInstance();
-  if (policy->CanReadFile(source->GetProcess()->GetID(), path)) {
-    // TODO(nick): |this| param in the call below ought to be a RenderFrameHost.
-    delegate_->EnumerateDirectory(this, request_id, path);
-  }
+  if (!policy->CanReadFile(source->GetProcess()->GetID(), path))
+    return;
+  auto listener = std::make_unique<ViewFileSelectListener>(this, request_id);
+  // TODO(nick): |this| param in the call below ought to be a RenderFrameHost.
+  delegate_->EnumerateDirectory(this, std::move(listener), path);
 }
 
 void WebContentsImpl::OnRegisterProtocolHandler(RenderFrameHostImpl* source,
@@ -5254,13 +5296,16 @@
 
 void WebContentsImpl::RunFileChooser(
     RenderFrameHost* render_frame_host,
+    std::unique_ptr<content::FileSelectListener> listener,
     const blink::mojom::FileChooserParams& params) {
   // Any explicit focusing of another window while this WebContents is in
   // fullscreen can be used to confuse the user, so drop fullscreen.
   ForSecurityDropFullscreen();
 
   if (delegate_)
-    delegate_->RunFileChooser(render_frame_host, params);
+    delegate_->RunFileChooser(render_frame_host, std::move(listener), params);
+  else
+    listener->FileSelectionCanceled();
 }
 
 WebContents* WebContentsImpl::GetAsWebContents() {
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 44f9ba81..ddde2f7 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -498,6 +498,7 @@
                               bool is_reload,
                               IPC::Message* reply_msg) override;
   void RunFileChooser(RenderFrameHost* render_frame_host,
+                      std::unique_ptr<content::FileSelectListener> listener,
                       const blink::mojom::FileChooserParams& params) override;
   void DidCancelLoading() override;
   void DidAccessInitialDocument() override;
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 13e32d0..b814f64 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -27,6 +27,7 @@
 #include "content/browser/web_contents/web_contents_view.h"
 #include "content/common/frame_messages.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/invalidate_type.h"
 #include "content/public/browser/javascript_dialog_manager.h"
 #include "content/public/browser/load_notification_details.h"
@@ -1637,6 +1638,14 @@
   DISALLOW_COPY_AND_ASSIGN(TestWCDelegateForDialogsAndFullscreen);
 };
 
+class MockFileSelectListener : public FileSelectListener {
+ public:
+  MockFileSelectListener() {}
+  void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files,
+                    blink::mojom::FileChooserParams::Mode mode) override {}
+  void FileSelectionCanceled() override {}
+};
+
 }  // namespace
 
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
@@ -2216,7 +2225,9 @@
 
   wc->EnterFullscreenMode(url, blink::WebFullscreenOptions());
   EXPECT_TRUE(wc->IsFullscreenForCurrentTab());
-  wc->RunFileChooser(wc->GetMainFrame(), blink::mojom::FileChooserParams());
+  wc->RunFileChooser(wc->GetMainFrame(),
+                     std::make_unique<MockFileSelectListener>(),
+                     blink::mojom::FileChooserParams());
   EXPECT_FALSE(wc->IsFullscreenForCurrentTab());
 
   wc->SetDelegate(nullptr);
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 3e97d4a..6f54db5 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -131,6 +131,7 @@
     "download_utils.h",
     "favicon_status.cc",
     "favicon_status.h",
+    "file_select_listener.h",
     "file_url_loader.h",
     "focused_node_details.h",
     "font_list_async.h",
diff --git a/content/public/browser/file_select_listener.h b/content/public/browser/file_select_listener.h
new file mode 100644
index 0000000..a9be393
--- /dev/null
+++ b/content/public/browser/file_select_listener.h
@@ -0,0 +1,31 @@
+// Copyright 2018 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.
+
+#ifndef CONTENT_PUBLIC_BROWSER_FILE_SELECT_LISTENER_H_
+#define CONTENT_PUBLIC_BROWSER_FILE_SELECT_LISTENER_H_
+
+#include <vector>
+
+#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
+
+namespace content {
+
+// Callback interface to receive results of RunFileChooser() and
+// EnumerateDirectory() of WebContentsDelegate.
+class FileSelectListener {
+ public:
+  virtual ~FileSelectListener() {}
+
+  // This function should be called if file selection succeeds.
+  virtual void FileSelected(
+      std::vector<blink::mojom::FileChooserFileInfoPtr> files,
+      blink::mojom::FileChooserParams::Mode mode) = 0;
+
+  // This function should be called if a user cancels a file selection
+  // dialog, or we open no file selection dialog for some reason.
+  virtual void FileSelectionCanceled() = 0;
+};
+
+}  // namespace content
+#endif  // CONTENT_PUBLIC_BROWSER_FILE_SELECT_LISTENER_H_
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index ce6a283..fb2c98ed 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -15,7 +15,6 @@
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_sender.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
-#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
 #include "third_party/blink/public/mojom/loader/pause_subresource_loading_handle.mojom.h"
 #include "third_party/blink/public/mojom/page/page_visibility_state.mojom.h"
 #include "third_party/blink/public/platform/web_sudden_termination_disabler_type.h"
@@ -249,13 +248,6 @@
   // use by resource metrics.
   virtual int GetProxyCount() = 0;
 
-  // Notifies the Listener that one or more files have been chosen by the user
-  // from a file chooser dialog for the form. |permissions| is the file
-  // selection mode in which the chooser dialog was created.
-  virtual void FilesSelectedInChooser(
-      const std::vector<blink::mojom::FileChooserFileInfoPtr>& files,
-      blink::mojom::FileChooserParams::Mode permissions) = 0;
-
   // Returns true if the frame has a selection.
   virtual bool HasSelection() = 0;
 
diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc
index 2467ba7..a216418 100644
--- a/content/public/browser/web_contents_delegate.cc
+++ b/content/public/browser/web_contents_delegate.cc
@@ -11,6 +11,7 @@
 #include "base/memory/singleton.h"
 #include "build/build_config.h"
 #include "components/viz/common/surfaces/surface_id.h"
+#include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/security_style_explanations.h"
@@ -158,6 +159,20 @@
   return nullptr;
 }
 
+void WebContentsDelegate::RunFileChooser(
+    RenderFrameHost* render_frame_host,
+    std::unique_ptr<FileSelectListener> listener,
+    const blink::mojom::FileChooserParams& params) {
+  listener->FileSelectionCanceled();
+}
+
+void WebContentsDelegate::EnumerateDirectory(
+    WebContents* web_contents,
+    std::unique_ptr<FileSelectListener> listener,
+    const base::FilePath& path) {
+  listener->FileSelectionCanceled();
+}
+
 void WebContentsDelegate::RequestMediaAccessPermission(
     WebContents* web_contents,
     const MediaStreamRequest& request,
diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h
index dfc941b..df6045d 100644
--- a/content/public/browser/web_contents_delegate.h
+++ b/content/public/browser/web_contents_delegate.h
@@ -42,8 +42,15 @@
 class FilePath;
 }
 
+namespace blink {
+namespace mojom {
+class FileChooserParams;
+}
+}  // namespace blink
+
 namespace content {
 class ColorChooser;
+class FileSelectListener;
 class JavaScriptDialogManager;
 class RenderFrameHost;
 class RenderProcessHost;
@@ -363,15 +370,20 @@
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions);
 
   // Called when a file selection is to be done.
+  // This function is responsible for calling listener->FileSelected() or
+  // listener->FileSelectionCanceled().
   virtual void RunFileChooser(RenderFrameHost* render_frame_host,
-                              const blink::mojom::FileChooserParams& params) {}
+                              std::unique_ptr<FileSelectListener> listener,
+                              const blink::mojom::FileChooserParams& params);
 
   // Request to enumerate a directory.  This is equivalent to running the file
   // chooser in directory-enumeration mode and having the user select the given
   // directory.
+  // This function is responsible for calling listener->FileSelected() or
+  // listener->FileSelectionCanceled().
   virtual void EnumerateDirectory(WebContents* web_contents,
-                                  int request_id,
-                                  const base::FilePath& path) {}
+                                  std::unique_ptr<FileSelectListener> listener,
+                                  const base::FilePath& path);
 
   // Shows a chooser for the user to select a nearby Bluetooth device. The
   // observer must live at least as long as the returned chooser object.
diff --git a/content/test/content_browser_test_utils_internal.cc b/content/test/content_browser_test_utils_internal.cc
index 41d0c24..af9d280 100644
--- a/content/test/content_browser_test_utils_internal.cc
+++ b/content/test/content_browser_test_utils_internal.cc
@@ -27,6 +27,7 @@
 #include "content/common/view_messages.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
@@ -288,14 +289,15 @@
 
 void FileChooserDelegate::RunFileChooser(
     RenderFrameHost* render_frame_host,
+    std::unique_ptr<content::FileSelectListener> listener,
     const blink::mojom::FileChooserParams& params) {
   // Send the selected file to the renderer process.
   auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
       blink::mojom::NativeFileInfo::New(file_, base::string16()));
   std::vector<blink::mojom::FileChooserFileInfoPtr> files;
   files.push_back(std::move(file_info));
-  render_frame_host->FilesSelectedInChooser(
-      files, blink::mojom::FileChooserParams::Mode::kOpen);
+  listener->FileSelected(std::move(files),
+                         blink::mojom::FileChooserParams::Mode::kOpen);
 
   file_chosen_ = true;
   params_ = params.Clone();
diff --git a/content/test/content_browser_test_utils_internal.h b/content/test/content_browser_test_utils_internal.h
index 42be8e6..d8f46a7d 100644
--- a/content/test/content_browser_test_utils_internal.h
+++ b/content/test/content_browser_test_utils_internal.h
@@ -116,6 +116,7 @@
 
   // Implementation of WebContentsDelegate::RunFileChooser.
   void RunFileChooser(RenderFrameHost* render_frame_host,
+                      std::unique_ptr<content::FileSelectListener> listener,
                       const blink::mojom::FileChooserParams& params) override;
 
   // Whether the file dialog was shown.