• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Embedded Framework Authors.
2 // Portions copyright (c) 2012 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 
6 #include "libcef/browser/file_dialog_manager.h"
7 
8 #include <utility>
9 
10 #include "include/cef_dialog_handler.h"
11 #include "libcef/browser/alloy/alloy_browser_host_impl.h"
12 #include "libcef/browser/thread_util.h"
13 
14 #include "content/public/browser/file_select_listener.h"
15 #include "content/public/browser/render_frame_host.h"
16 #include "net/base/directory_lister.h"
17 
18 namespace {
19 
20 class CefFileDialogCallbackImpl : public CefFileDialogCallback {
21  public:
22   using CallbackType = CefFileDialogRunner::RunFileChooserCallback;
23 
CefFileDialogCallbackImpl(CallbackType callback)24   explicit CefFileDialogCallbackImpl(CallbackType callback)
25       : callback_(std::move(callback)) {}
26 
~CefFileDialogCallbackImpl()27   ~CefFileDialogCallbackImpl() override {
28     if (!callback_.is_null()) {
29       // The callback is still pending. Cancel it now.
30       if (CEF_CURRENTLY_ON_UIT()) {
31         CancelNow(std::move(callback_));
32       } else {
33         CEF_POST_TASK(CEF_UIT,
34                       base::BindOnce(&CefFileDialogCallbackImpl::CancelNow,
35                                      std::move(callback_)));
36       }
37     }
38   }
39 
Continue(int selected_accept_filter,const std::vector<CefString> & file_paths)40   void Continue(int selected_accept_filter,
41                 const std::vector<CefString>& file_paths) override {
42     if (CEF_CURRENTLY_ON_UIT()) {
43       if (!callback_.is_null()) {
44         std::vector<base::FilePath> vec;
45         if (!file_paths.empty()) {
46           std::vector<CefString>::const_iterator it = file_paths.begin();
47           for (; it != file_paths.end(); ++it)
48             vec.push_back(base::FilePath(*it));
49         }
50         std::move(callback_).Run(selected_accept_filter, vec);
51       }
52     } else {
53       CEF_POST_TASK(CEF_UIT,
54                     base::BindOnce(&CefFileDialogCallbackImpl::Continue, this,
55                                    selected_accept_filter, file_paths));
56     }
57   }
58 
Cancel()59   void Cancel() override {
60     if (CEF_CURRENTLY_ON_UIT()) {
61       if (!callback_.is_null()) {
62         CancelNow(std::move(callback_));
63       }
64     } else {
65       CEF_POST_TASK(CEF_UIT,
66                     base::BindOnce(&CefFileDialogCallbackImpl::Cancel, this));
67     }
68   }
69 
Disconnect()70   CallbackType Disconnect() WARN_UNUSED_RESULT { return std::move(callback_); }
71 
72  private:
CancelNow(CallbackType callback)73   static void CancelNow(CallbackType callback) {
74     CEF_REQUIRE_UIT();
75     std::vector<base::FilePath> file_paths;
76     std::move(callback).Run(0, file_paths);
77   }
78 
79   CallbackType callback_;
80 
81   IMPLEMENT_REFCOUNTING(CefFileDialogCallbackImpl);
82 };
83 
RunFileDialogDismissed(CefRefPtr<CefRunFileDialogCallback> callback,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)84 void RunFileDialogDismissed(CefRefPtr<CefRunFileDialogCallback> callback,
85                             int selected_accept_filter,
86                             const std::vector<base::FilePath>& file_paths) {
87   std::vector<CefString> paths;
88   if (file_paths.size() > 0) {
89     for (size_t i = 0; i < file_paths.size(); ++i)
90       paths.push_back(file_paths[i].value());
91   }
92   callback->OnFileDialogDismissed(selected_accept_filter, paths);
93 }
94 
95 class UploadFolderHelper
96     : public net::DirectoryLister::DirectoryListerDelegate {
97  public:
UploadFolderHelper(CefFileDialogRunner::RunFileChooserCallback callback)98   explicit UploadFolderHelper(
99       CefFileDialogRunner::RunFileChooserCallback callback)
100       : callback_(std::move(callback)) {}
101 
~UploadFolderHelper()102   ~UploadFolderHelper() override {
103     if (!callback_.is_null()) {
104       if (CEF_CURRENTLY_ON_UIT()) {
105         CancelNow(std::move(callback_));
106       } else {
107         CEF_POST_TASK(CEF_UIT, base::BindOnce(&UploadFolderHelper::CancelNow,
108                                               std::move(callback_)));
109       }
110     }
111   }
112 
OnListFile(const net::DirectoryLister::DirectoryListerData & data)113   void OnListFile(
114       const net::DirectoryLister::DirectoryListerData& data) override {
115     CEF_REQUIRE_UIT();
116     if (!data.info.IsDirectory())
117       select_files_.push_back(data.path);
118   }
119 
OnListDone(int error)120   void OnListDone(int error) override {
121     CEF_REQUIRE_UIT();
122     if (!callback_.is_null()) {
123       std::move(callback_).Run(0, select_files_);
124     }
125   }
126 
127  private:
CancelNow(CefFileDialogRunner::RunFileChooserCallback callback)128   static void CancelNow(CefFileDialogRunner::RunFileChooserCallback callback) {
129     CEF_REQUIRE_UIT();
130     std::vector<base::FilePath> file_paths;
131     std::move(callback).Run(0, file_paths);
132   }
133 
134   CefFileDialogRunner::RunFileChooserCallback callback_;
135   std::vector<base::FilePath> select_files_;
136 
137   DISALLOW_COPY_AND_ASSIGN(UploadFolderHelper);
138 };
139 
140 }  // namespace
141 
CefFileDialogManager(AlloyBrowserHostImpl * browser,std::unique_ptr<CefFileDialogRunner> runner)142 CefFileDialogManager::CefFileDialogManager(
143     AlloyBrowserHostImpl* browser,
144     std::unique_ptr<CefFileDialogRunner> runner)
145     : browser_(browser),
146       runner_(std::move(runner)),
147       file_chooser_pending_(false),
148       weak_ptr_factory_(this) {}
149 
~CefFileDialogManager()150 CefFileDialogManager::~CefFileDialogManager() {}
151 
Destroy()152 void CefFileDialogManager::Destroy() {
153   DCHECK(!file_chooser_pending_);
154   runner_.reset(nullptr);
155 }
156 
RunFileDialog(cef_file_dialog_mode_t mode,const CefString & title,const CefString & default_file_path,const std::vector<CefString> & accept_filters,int selected_accept_filter,CefRefPtr<CefRunFileDialogCallback> callback)157 void CefFileDialogManager::RunFileDialog(
158     cef_file_dialog_mode_t mode,
159     const CefString& title,
160     const CefString& default_file_path,
161     const std::vector<CefString>& accept_filters,
162     int selected_accept_filter,
163     CefRefPtr<CefRunFileDialogCallback> callback) {
164   DCHECK(callback.get());
165   if (!callback.get())
166     return;
167 
168   CefFileDialogRunner::FileChooserParams params;
169   switch (mode & FILE_DIALOG_TYPE_MASK) {
170     case FILE_DIALOG_OPEN:
171       params.mode = blink::mojom::FileChooserParams::Mode::kOpen;
172       break;
173     case FILE_DIALOG_OPEN_MULTIPLE:
174       params.mode = blink::mojom::FileChooserParams::Mode::kOpenMultiple;
175       break;
176     case FILE_DIALOG_OPEN_FOLDER:
177       params.mode = blink::mojom::FileChooserParams::Mode::kUploadFolder;
178       break;
179     case FILE_DIALOG_SAVE:
180       params.mode = blink::mojom::FileChooserParams::Mode::kSave;
181       break;
182   }
183 
184   DCHECK_GE(selected_accept_filter, 0);
185   params.selected_accept_filter = selected_accept_filter;
186 
187   params.overwriteprompt = !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG);
188   params.hidereadonly = !!(mode & FILE_DIALOG_HIDEREADONLY_FLAG);
189 
190   params.title = title;
191   if (!default_file_path.empty())
192     params.default_file_name = base::FilePath(default_file_path);
193 
194   if (!accept_filters.empty()) {
195     std::vector<CefString>::const_iterator it = accept_filters.begin();
196     for (; it != accept_filters.end(); ++it)
197       params.accept_types.push_back(*it);
198   }
199 
200   RunFileChooser(params, base::Bind(RunFileDialogDismissed, callback));
201 }
202 
RunFileChooser(scoped_refptr<content::FileSelectListener> listener,const blink::mojom::FileChooserParams & params)203 void CefFileDialogManager::RunFileChooser(
204     scoped_refptr<content::FileSelectListener> listener,
205     const blink::mojom::FileChooserParams& params) {
206   CEF_REQUIRE_UIT();
207 
208   CefFileDialogRunner::FileChooserParams cef_params;
209   static_cast<blink::mojom::FileChooserParams&>(cef_params) = params;
210 
211   CefFileDialogRunner::RunFileChooserCallback callback;
212   if (params.mode == blink::mojom::FileChooserParams::Mode::kUploadFolder) {
213     callback = base::BindOnce(
214         &CefFileDialogManager::OnRunFileChooserUploadFolderDelegateCallback,
215         weak_ptr_factory_.GetWeakPtr(), params.mode, listener);
216   } else {
217     callback =
218         base::BindOnce(&CefFileDialogManager::OnRunFileChooserDelegateCallback,
219                        weak_ptr_factory_.GetWeakPtr(), params.mode, listener);
220   }
221 
222   RunFileChooserInternal(cef_params, std::move(callback));
223 }
224 
RunFileChooser(const CefFileDialogRunner::FileChooserParams & params,CefFileDialogRunner::RunFileChooserCallback callback)225 void CefFileDialogManager::RunFileChooser(
226     const CefFileDialogRunner::FileChooserParams& params,
227     CefFileDialogRunner::RunFileChooserCallback callback) {
228   CefFileDialogRunner::RunFileChooserCallback host_callback =
229       base::BindOnce(&CefFileDialogManager::OnRunFileChooserCallback,
230                      weak_ptr_factory_.GetWeakPtr(), std::move(callback));
231   RunFileChooserInternal(params, std::move(host_callback));
232 }
233 
RunFileChooserInternal(const CefFileDialogRunner::FileChooserParams & params,CefFileDialogRunner::RunFileChooserCallback callback)234 void CefFileDialogManager::RunFileChooserInternal(
235     const CefFileDialogRunner::FileChooserParams& params,
236     CefFileDialogRunner::RunFileChooserCallback callback) {
237   CEF_REQUIRE_UIT();
238 
239   if (file_chooser_pending_) {
240     // Dismiss the new dialog immediately.
241     std::move(callback).Run(0, std::vector<base::FilePath>());
242     return;
243   }
244 
245   file_chooser_pending_ = true;
246 
247   bool handled = false;
248 
249   if (browser_->client().get()) {
250     CefRefPtr<CefDialogHandler> handler =
251         browser_->client()->GetDialogHandler();
252     if (handler.get()) {
253       int mode = FILE_DIALOG_OPEN;
254       switch (params.mode) {
255         case blink::mojom::FileChooserParams::Mode::kOpen:
256           mode = FILE_DIALOG_OPEN;
257           break;
258         case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
259           mode = FILE_DIALOG_OPEN_MULTIPLE;
260           break;
261         case blink::mojom::FileChooserParams::Mode::kUploadFolder:
262           mode = FILE_DIALOG_OPEN_FOLDER;
263           break;
264         case blink::mojom::FileChooserParams::Mode::kSave:
265           mode = FILE_DIALOG_SAVE;
266           break;
267         default:
268           NOTREACHED();
269           break;
270       }
271 
272       if (params.overwriteprompt)
273         mode |= FILE_DIALOG_OVERWRITEPROMPT_FLAG;
274       if (params.hidereadonly)
275         mode |= FILE_DIALOG_HIDEREADONLY_FLAG;
276 
277       std::vector<std::u16string>::const_iterator it;
278 
279       std::vector<CefString> accept_filters;
280       it = params.accept_types.begin();
281       for (; it != params.accept_types.end(); ++it)
282         accept_filters.push_back(*it);
283 
284       CefRefPtr<CefFileDialogCallbackImpl> callbackImpl(
285           new CefFileDialogCallbackImpl(std::move(callback)));
286       handled = handler->OnFileDialog(
287           browser_, static_cast<cef_file_dialog_mode_t>(mode), params.title,
288           params.default_file_name.value(), accept_filters,
289           params.selected_accept_filter, callbackImpl.get());
290       if (!handled) {
291         // May return nullptr if the client has already executed the callback.
292         callback = callbackImpl->Disconnect();
293       }
294     }
295   }
296 
297   if (!handled && !callback.is_null()) {
298     if (runner_.get()) {
299       runner_->Run(browser_, params, std::move(callback));
300     } else {
301       LOG(WARNING) << "No file dialog runner available for this platform";
302       std::move(callback).Run(0, std::vector<base::FilePath>());
303     }
304   }
305 }
306 
OnRunFileChooserCallback(CefFileDialogRunner::RunFileChooserCallback callback,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)307 void CefFileDialogManager::OnRunFileChooserCallback(
308     CefFileDialogRunner::RunFileChooserCallback callback,
309     int selected_accept_filter,
310     const std::vector<base::FilePath>& file_paths) {
311   CEF_REQUIRE_UIT();
312 
313   Cleanup();
314 
315   // Execute the callback asynchronously.
316   CEF_POST_TASK(CEF_UIT, base::BindOnce(std::move(callback),
317                                         selected_accept_filter, file_paths));
318 }
319 
OnRunFileChooserUploadFolderDelegateCallback(const blink::mojom::FileChooserParams::Mode mode,scoped_refptr<content::FileSelectListener> listener,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)320 void CefFileDialogManager::OnRunFileChooserUploadFolderDelegateCallback(
321     const blink::mojom::FileChooserParams::Mode mode,
322     scoped_refptr<content::FileSelectListener> listener,
323     int selected_accept_filter,
324     const std::vector<base::FilePath>& file_paths) {
325   CEF_REQUIRE_UIT();
326   DCHECK_EQ(mode, blink::mojom::FileChooserParams::Mode::kUploadFolder);
327 
328   if (file_paths.size() == 0) {
329     // Client canceled the file chooser.
330     OnRunFileChooserDelegateCallback(mode, listener, selected_accept_filter,
331                                      file_paths);
332   } else {
333     lister_.reset(new net::DirectoryLister(
334         file_paths[0], net::DirectoryLister::NO_SORT_RECURSIVE,
335         new UploadFolderHelper(base::BindOnce(
336             &CefFileDialogManager::OnRunFileChooserDelegateCallback,
337             weak_ptr_factory_.GetWeakPtr(), mode, listener))));
338     lister_->Start();
339   }
340 }
341 
OnRunFileChooserDelegateCallback(blink::mojom::FileChooserParams::Mode mode,scoped_refptr<content::FileSelectListener> listener,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)342 void CefFileDialogManager::OnRunFileChooserDelegateCallback(
343     blink::mojom::FileChooserParams::Mode mode,
344     scoped_refptr<content::FileSelectListener> listener,
345     int selected_accept_filter,
346     const std::vector<base::FilePath>& file_paths) {
347   CEF_REQUIRE_UIT();
348 
349   base::FilePath base_dir;
350   std::vector<blink::mojom::FileChooserFileInfoPtr> selected_files;
351 
352   if (!file_paths.empty()) {
353     if (mode == blink::mojom::FileChooserParams::Mode::kUploadFolder) {
354       base_dir = file_paths[0].DirName();
355     }
356 
357     // Convert FilePath list to SelectedFileInfo list.
358     for (size_t i = 0; i < file_paths.size(); ++i) {
359       auto info = blink::mojom::FileChooserFileInfo::NewNativeFile(
360           blink::mojom::NativeFileInfo::New(file_paths[i], std::u16string()));
361       selected_files.push_back(std::move(info));
362     }
363   }
364 
365   listener->FileSelected(std::move(selected_files), base_dir, mode);
366 
367   Cleanup();
368 }
369 
Cleanup()370 void CefFileDialogManager::Cleanup() {
371   if (lister_)
372     lister_.reset();
373 
374   file_chooser_pending_ = false;
375 }
376