• 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 
102   UploadFolderHelper(const UploadFolderHelper&) = delete;
103   UploadFolderHelper& operator=(const UploadFolderHelper&) = delete;
104 
~UploadFolderHelper()105   ~UploadFolderHelper() override {
106     if (!callback_.is_null()) {
107       if (CEF_CURRENTLY_ON_UIT()) {
108         CancelNow(std::move(callback_));
109       } else {
110         CEF_POST_TASK(CEF_UIT, base::BindOnce(&UploadFolderHelper::CancelNow,
111                                               std::move(callback_)));
112       }
113     }
114   }
115 
OnListFile(const net::DirectoryLister::DirectoryListerData & data)116   void OnListFile(
117       const net::DirectoryLister::DirectoryListerData& data) override {
118     CEF_REQUIRE_UIT();
119     if (!data.info.IsDirectory())
120       select_files_.push_back(data.path);
121   }
122 
OnListDone(int error)123   void OnListDone(int error) override {
124     CEF_REQUIRE_UIT();
125     if (!callback_.is_null()) {
126       std::move(callback_).Run(0, select_files_);
127     }
128   }
129 
130  private:
CancelNow(CefFileDialogRunner::RunFileChooserCallback callback)131   static void CancelNow(CefFileDialogRunner::RunFileChooserCallback callback) {
132     CEF_REQUIRE_UIT();
133     std::vector<base::FilePath> file_paths;
134     std::move(callback).Run(0, file_paths);
135   }
136 
137   CefFileDialogRunner::RunFileChooserCallback callback_;
138   std::vector<base::FilePath> select_files_;
139 };
140 
141 }  // namespace
142 
CefFileDialogManager(AlloyBrowserHostImpl * browser,std::unique_ptr<CefFileDialogRunner> runner)143 CefFileDialogManager::CefFileDialogManager(
144     AlloyBrowserHostImpl* browser,
145     std::unique_ptr<CefFileDialogRunner> runner)
146     : browser_(browser),
147       runner_(std::move(runner)),
148       file_chooser_pending_(false),
149       weak_ptr_factory_(this) {}
150 
~CefFileDialogManager()151 CefFileDialogManager::~CefFileDialogManager() {}
152 
Destroy()153 void CefFileDialogManager::Destroy() {
154   DCHECK(!file_chooser_pending_);
155   runner_.reset(nullptr);
156 }
157 
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)158 void CefFileDialogManager::RunFileDialog(
159     cef_file_dialog_mode_t mode,
160     const CefString& title,
161     const CefString& default_file_path,
162     const std::vector<CefString>& accept_filters,
163     int selected_accept_filter,
164     CefRefPtr<CefRunFileDialogCallback> callback) {
165   DCHECK(callback.get());
166   if (!callback.get())
167     return;
168 
169   CefFileDialogRunner::FileChooserParams params;
170   switch (mode & FILE_DIALOG_TYPE_MASK) {
171     case FILE_DIALOG_OPEN:
172       params.mode = blink::mojom::FileChooserParams::Mode::kOpen;
173       break;
174     case FILE_DIALOG_OPEN_MULTIPLE:
175       params.mode = blink::mojom::FileChooserParams::Mode::kOpenMultiple;
176       break;
177     case FILE_DIALOG_OPEN_FOLDER:
178       params.mode = blink::mojom::FileChooserParams::Mode::kUploadFolder;
179       break;
180     case FILE_DIALOG_SAVE:
181       params.mode = blink::mojom::FileChooserParams::Mode::kSave;
182       break;
183   }
184 
185   DCHECK_GE(selected_accept_filter, 0);
186   params.selected_accept_filter = selected_accept_filter;
187 
188   params.overwriteprompt = !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG);
189   params.hidereadonly = !!(mode & FILE_DIALOG_HIDEREADONLY_FLAG);
190 
191   params.title = title;
192   if (!default_file_path.empty())
193     params.default_file_name = base::FilePath(default_file_path);
194 
195   if (!accept_filters.empty()) {
196     std::vector<CefString>::const_iterator it = accept_filters.begin();
197     for (; it != accept_filters.end(); ++it)
198       params.accept_types.push_back(*it);
199   }
200 
201   RunFileChooser(params, base::BindOnce(RunFileDialogDismissed, callback));
202 }
203 
RunFileChooser(scoped_refptr<content::FileSelectListener> listener,const blink::mojom::FileChooserParams & params)204 void CefFileDialogManager::RunFileChooser(
205     scoped_refptr<content::FileSelectListener> listener,
206     const blink::mojom::FileChooserParams& params) {
207   CEF_REQUIRE_UIT();
208 
209   CefFileDialogRunner::FileChooserParams cef_params;
210   static_cast<blink::mojom::FileChooserParams&>(cef_params) = params;
211 
212   CefFileDialogRunner::RunFileChooserCallback callback;
213   if (params.mode == blink::mojom::FileChooserParams::Mode::kUploadFolder) {
214     callback = base::BindOnce(
215         &CefFileDialogManager::OnRunFileChooserUploadFolderDelegateCallback,
216         weak_ptr_factory_.GetWeakPtr(), params.mode, listener);
217   } else {
218     callback =
219         base::BindOnce(&CefFileDialogManager::OnRunFileChooserDelegateCallback,
220                        weak_ptr_factory_.GetWeakPtr(), params.mode, listener);
221   }
222 
223   RunFileChooserInternal(cef_params, std::move(callback));
224 }
225 
RunFileChooser(const CefFileDialogRunner::FileChooserParams & params,CefFileDialogRunner::RunFileChooserCallback callback)226 void CefFileDialogManager::RunFileChooser(
227     const CefFileDialogRunner::FileChooserParams& params,
228     CefFileDialogRunner::RunFileChooserCallback callback) {
229   CefFileDialogRunner::RunFileChooserCallback host_callback =
230       base::BindOnce(&CefFileDialogManager::OnRunFileChooserCallback,
231                      weak_ptr_factory_.GetWeakPtr(), std::move(callback));
232   RunFileChooserInternal(params, std::move(host_callback));
233 }
234 
RunFileChooserInternal(const CefFileDialogRunner::FileChooserParams & params,CefFileDialogRunner::RunFileChooserCallback callback)235 void CefFileDialogManager::RunFileChooserInternal(
236     const CefFileDialogRunner::FileChooserParams& params,
237     CefFileDialogRunner::RunFileChooserCallback callback) {
238   CEF_REQUIRE_UIT();
239 
240   if (file_chooser_pending_) {
241     // Dismiss the new dialog immediately.
242     std::move(callback).Run(0, std::vector<base::FilePath>());
243     return;
244   }
245 
246   file_chooser_pending_ = true;
247 
248   bool handled = false;
249 
250   if (browser_->client().get()) {
251     CefRefPtr<CefDialogHandler> handler =
252         browser_->client()->GetDialogHandler();
253     if (handler.get()) {
254       int mode = FILE_DIALOG_OPEN;
255       switch (params.mode) {
256         case blink::mojom::FileChooserParams::Mode::kOpen:
257           mode = FILE_DIALOG_OPEN;
258           break;
259         case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
260           mode = FILE_DIALOG_OPEN_MULTIPLE;
261           break;
262         case blink::mojom::FileChooserParams::Mode::kUploadFolder:
263           mode = FILE_DIALOG_OPEN_FOLDER;
264           break;
265         case blink::mojom::FileChooserParams::Mode::kSave:
266           mode = FILE_DIALOG_SAVE;
267           break;
268         default:
269           NOTREACHED();
270           break;
271       }
272 
273       if (params.overwriteprompt)
274         mode |= FILE_DIALOG_OVERWRITEPROMPT_FLAG;
275       if (params.hidereadonly)
276         mode |= FILE_DIALOG_HIDEREADONLY_FLAG;
277 
278       std::vector<std::u16string>::const_iterator it;
279 
280       std::vector<CefString> accept_filters;
281       it = params.accept_types.begin();
282       for (; it != params.accept_types.end(); ++it)
283         accept_filters.push_back(*it);
284 
285       CefRefPtr<CefFileDialogCallbackImpl> callbackImpl(
286           new CefFileDialogCallbackImpl(std::move(callback)));
287       handled = handler->OnFileDialog(
288           browser_, static_cast<cef_file_dialog_mode_t>(mode), params.title,
289           params.default_file_name.value(), accept_filters,
290           params.selected_accept_filter, callbackImpl.get());
291       if (!handled) {
292         // May return nullptr if the client has already executed the callback.
293         callback = callbackImpl->Disconnect();
294       }
295     }
296   }
297 
298   if (!handled && !callback.is_null()) {
299     if (runner_.get()) {
300       runner_->Run(browser_, params, std::move(callback));
301     } else {
302       LOG(WARNING) << "No file dialog runner available for this platform";
303       std::move(callback).Run(0, std::vector<base::FilePath>());
304     }
305   }
306 }
307 
OnRunFileChooserCallback(CefFileDialogRunner::RunFileChooserCallback callback,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)308 void CefFileDialogManager::OnRunFileChooserCallback(
309     CefFileDialogRunner::RunFileChooserCallback callback,
310     int selected_accept_filter,
311     const std::vector<base::FilePath>& file_paths) {
312   CEF_REQUIRE_UIT();
313 
314   Cleanup();
315 
316   // Execute the callback asynchronously.
317   CEF_POST_TASK(CEF_UIT, base::BindOnce(std::move(callback),
318                                         selected_accept_filter, file_paths));
319 }
320 
OnRunFileChooserUploadFolderDelegateCallback(const blink::mojom::FileChooserParams::Mode mode,scoped_refptr<content::FileSelectListener> listener,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)321 void CefFileDialogManager::OnRunFileChooserUploadFolderDelegateCallback(
322     const blink::mojom::FileChooserParams::Mode mode,
323     scoped_refptr<content::FileSelectListener> listener,
324     int selected_accept_filter,
325     const std::vector<base::FilePath>& file_paths) {
326   CEF_REQUIRE_UIT();
327   DCHECK_EQ(mode, blink::mojom::FileChooserParams::Mode::kUploadFolder);
328 
329   if (file_paths.size() == 0) {
330     // Client canceled the file chooser.
331     OnRunFileChooserDelegateCallback(mode, listener, selected_accept_filter,
332                                      file_paths);
333   } else {
334     lister_.reset(new net::DirectoryLister(
335         file_paths[0], net::DirectoryLister::NO_SORT_RECURSIVE,
336         new UploadFolderHelper(base::BindOnce(
337             &CefFileDialogManager::OnRunFileChooserDelegateCallback,
338             weak_ptr_factory_.GetWeakPtr(), mode, listener))));
339     lister_->Start();
340   }
341 }
342 
OnRunFileChooserDelegateCallback(blink::mojom::FileChooserParams::Mode mode,scoped_refptr<content::FileSelectListener> listener,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)343 void CefFileDialogManager::OnRunFileChooserDelegateCallback(
344     blink::mojom::FileChooserParams::Mode mode,
345     scoped_refptr<content::FileSelectListener> listener,
346     int selected_accept_filter,
347     const std::vector<base::FilePath>& file_paths) {
348   CEF_REQUIRE_UIT();
349 
350   base::FilePath base_dir;
351   std::vector<blink::mojom::FileChooserFileInfoPtr> selected_files;
352 
353   if (!file_paths.empty()) {
354     if (mode == blink::mojom::FileChooserParams::Mode::kUploadFolder) {
355       base_dir = file_paths[0].DirName();
356     }
357 
358     // Convert FilePath list to SelectedFileInfo list.
359     for (size_t i = 0; i < file_paths.size(); ++i) {
360       auto info = blink::mojom::FileChooserFileInfo::NewNativeFile(
361           blink::mojom::NativeFileInfo::New(file_paths[i], std::u16string()));
362       selected_files.push_back(std::move(info));
363     }
364   }
365 
366   listener->FileSelected(std::move(selected_files), base_dir, mode);
367 
368   Cleanup();
369 }
370 
Cleanup()371 void CefFileDialogManager::Cleanup() {
372   if (lister_)
373     lister_.reset();
374 
375   file_chooser_pending_ = false;
376 }
377