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