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