| // Copyright (c) 2012 The Chromium Embedded Framework Authors. |
| // Portions copyright (c) 2012 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. |
| |
| #include "libcef/browser/file_dialog_manager.h" |
| |
| #include <utility> |
| |
| #include "include/cef_dialog_handler.h" |
| #include "libcef/browser/browser_host_impl.h" |
| #include "libcef/browser/thread_util.h" |
| |
| #include "content/public/browser/file_select_listener.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/common/file_chooser_file_info.h" |
| #include "net/base/directory_lister.h" |
| |
| namespace { |
| |
| class CefFileDialogCallbackImpl : public CefFileDialogCallback { |
| public: |
| using CallbackType = CefFileDialogRunner::RunFileChooserCallback; |
| |
| explicit CefFileDialogCallbackImpl(CallbackType callback) |
| : callback_(std::move(callback)) {} |
| |
| ~CefFileDialogCallbackImpl() override { |
| if (!callback_.is_null()) { |
| // The callback is still pending. Cancel it now. |
| if (CEF_CURRENTLY_ON_UIT()) { |
| CancelNow(std::move(callback_)); |
| } else { |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(&CefFileDialogCallbackImpl::CancelNow, |
| std::move(callback_))); |
| } |
| } |
| } |
| |
| void Continue(int selected_accept_filter, |
| const std::vector<CefString>& file_paths) override { |
| if (CEF_CURRENTLY_ON_UIT()) { |
| if (!callback_.is_null()) { |
| std::vector<base::FilePath> vec; |
| if (!file_paths.empty()) { |
| std::vector<CefString>::const_iterator it = file_paths.begin(); |
| for (; it != file_paths.end(); ++it) |
| vec.push_back(base::FilePath(*it)); |
| } |
| std::move(callback_).Run(selected_accept_filter, vec); |
| } |
| } else { |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(&CefFileDialogCallbackImpl::Continue, this, |
| selected_accept_filter, file_paths)); |
| } |
| } |
| |
| void Cancel() override { |
| if (CEF_CURRENTLY_ON_UIT()) { |
| if (!callback_.is_null()) { |
| CancelNow(std::move(callback_)); |
| } |
| } else { |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(&CefFileDialogCallbackImpl::Cancel, this)); |
| } |
| } |
| |
| CallbackType Disconnect() WARN_UNUSED_RESULT { return std::move(callback_); } |
| |
| private: |
| static void CancelNow(CallbackType callback) { |
| CEF_REQUIRE_UIT(); |
| std::vector<base::FilePath> file_paths; |
| std::move(callback).Run(0, file_paths); |
| } |
| |
| CallbackType callback_; |
| |
| IMPLEMENT_REFCOUNTING(CefFileDialogCallbackImpl); |
| }; |
| |
| void RunFileDialogDismissed(CefRefPtr<CefRunFileDialogCallback> callback, |
| int selected_accept_filter, |
| const std::vector<base::FilePath>& file_paths) { |
| std::vector<CefString> paths; |
| if (file_paths.size() > 0) { |
| for (size_t i = 0; i < file_paths.size(); ++i) |
| paths.push_back(file_paths[i].value()); |
| } |
| callback->OnFileDialogDismissed(selected_accept_filter, paths); |
| } |
| |
| class UploadFolderHelper |
| : public net::DirectoryLister::DirectoryListerDelegate { |
| public: |
| explicit UploadFolderHelper( |
| CefFileDialogRunner::RunFileChooserCallback callback) |
| : callback_(std::move(callback)) {} |
| |
| ~UploadFolderHelper() override { |
| if (!callback_.is_null()) { |
| if (CEF_CURRENTLY_ON_UIT()) { |
| CancelNow(std::move(callback_)); |
| } else { |
| CEF_POST_TASK(CEF_UIT, base::BindOnce(&UploadFolderHelper::CancelNow, |
| std::move(callback_))); |
| } |
| } |
| } |
| |
| void OnListFile( |
| const net::DirectoryLister::DirectoryListerData& data) override { |
| CEF_REQUIRE_UIT(); |
| if (!data.info.IsDirectory()) |
| select_files_.push_back(data.path); |
| } |
| |
| void OnListDone(int error) override { |
| CEF_REQUIRE_UIT(); |
| if (!callback_.is_null()) { |
| std::move(callback_).Run(0, select_files_); |
| } |
| } |
| |
| private: |
| static void CancelNow(CefFileDialogRunner::RunFileChooserCallback callback) { |
| CEF_REQUIRE_UIT(); |
| std::vector<base::FilePath> file_paths; |
| std::move(callback).Run(0, file_paths); |
| } |
| |
| CefFileDialogRunner::RunFileChooserCallback callback_; |
| std::vector<base::FilePath> select_files_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UploadFolderHelper); |
| }; |
| |
| } // namespace |
| |
| CefFileDialogManager::CefFileDialogManager( |
| CefBrowserHostImpl* browser, |
| std::unique_ptr<CefFileDialogRunner> runner) |
| : browser_(browser), |
| runner_(std::move(runner)), |
| file_chooser_pending_(false), |
| weak_ptr_factory_(this) {} |
| |
| CefFileDialogManager::~CefFileDialogManager() {} |
| |
| void CefFileDialogManager::Destroy() { |
| DCHECK(!file_chooser_pending_); |
| runner_.reset(nullptr); |
| } |
| |
| void CefFileDialogManager::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) { |
| DCHECK(callback.get()); |
| if (!callback.get()) |
| return; |
| |
| CefFileDialogRunner::FileChooserParams params; |
| switch (mode & FILE_DIALOG_TYPE_MASK) { |
| case FILE_DIALOG_OPEN: |
| params.mode = blink::mojom::FileChooserParams::Mode::kOpen; |
| break; |
| case FILE_DIALOG_OPEN_MULTIPLE: |
| params.mode = blink::mojom::FileChooserParams::Mode::kOpenMultiple; |
| break; |
| case FILE_DIALOG_OPEN_FOLDER: |
| params.mode = blink::mojom::FileChooserParams::Mode::kUploadFolder; |
| break; |
| case FILE_DIALOG_SAVE: |
| params.mode = blink::mojom::FileChooserParams::Mode::kSave; |
| break; |
| } |
| |
| DCHECK_GE(selected_accept_filter, 0); |
| params.selected_accept_filter = selected_accept_filter; |
| |
| params.overwriteprompt = !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG); |
| params.hidereadonly = !!(mode & FILE_DIALOG_HIDEREADONLY_FLAG); |
| |
| params.title = title; |
| if (!default_file_path.empty()) |
| params.default_file_name = base::FilePath(default_file_path); |
| |
| if (!accept_filters.empty()) { |
| std::vector<CefString>::const_iterator it = accept_filters.begin(); |
| for (; it != accept_filters.end(); ++it) |
| params.accept_types.push_back(*it); |
| } |
| |
| RunFileChooser(params, base::Bind(RunFileDialogDismissed, callback)); |
| } |
| |
| void CefFileDialogManager::RunFileChooser( |
| std::unique_ptr<content::FileSelectListener> listener, |
| const blink::mojom::FileChooserParams& params) { |
| CEF_REQUIRE_UIT(); |
| |
| CefFileDialogRunner::FileChooserParams cef_params; |
| static_cast<blink::mojom::FileChooserParams&>(cef_params) = params; |
| |
| CefFileDialogRunner::RunFileChooserCallback callback; |
| if (params.mode == blink::mojom::FileChooserParams::Mode::kUploadFolder) { |
| callback = base::BindOnce( |
| &CefFileDialogManager::OnRunFileChooserUploadFolderDelegateCallback, |
| weak_ptr_factory_.GetWeakPtr(), params.mode, std::move(listener)); |
| } else { |
| callback = base::BindOnce( |
| &CefFileDialogManager::OnRunFileChooserDelegateCallback, |
| weak_ptr_factory_.GetWeakPtr(), params.mode, std::move(listener)); |
| } |
| |
| RunFileChooserInternal(cef_params, std::move(callback)); |
| } |
| |
| void CefFileDialogManager::RunFileChooser( |
| const CefFileDialogRunner::FileChooserParams& params, |
| CefFileDialogRunner::RunFileChooserCallback callback) { |
| CefFileDialogRunner::RunFileChooserCallback host_callback = |
| base::BindOnce(&CefFileDialogManager::OnRunFileChooserCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)); |
| RunFileChooserInternal(params, std::move(host_callback)); |
| } |
| |
| void CefFileDialogManager::RunFileChooserInternal( |
| const CefFileDialogRunner::FileChooserParams& params, |
| CefFileDialogRunner::RunFileChooserCallback callback) { |
| CEF_REQUIRE_UIT(); |
| |
| if (file_chooser_pending_) { |
| // Dismiss the new dialog immediately. |
| std::move(callback).Run(0, std::vector<base::FilePath>()); |
| return; |
| } |
| |
| file_chooser_pending_ = true; |
| |
| bool handled = false; |
| |
| if (browser_->client().get()) { |
| CefRefPtr<CefDialogHandler> handler = |
| browser_->client()->GetDialogHandler(); |
| if (handler.get()) { |
| int mode = FILE_DIALOG_OPEN; |
| switch (params.mode) { |
| case blink::mojom::FileChooserParams::Mode::kOpen: |
| mode = FILE_DIALOG_OPEN; |
| break; |
| case blink::mojom::FileChooserParams::Mode::kOpenMultiple: |
| mode = FILE_DIALOG_OPEN_MULTIPLE; |
| break; |
| case blink::mojom::FileChooserParams::Mode::kUploadFolder: |
| mode = FILE_DIALOG_OPEN_FOLDER; |
| break; |
| case blink::mojom::FileChooserParams::Mode::kSave: |
| mode = FILE_DIALOG_SAVE; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| if (params.overwriteprompt) |
| mode |= FILE_DIALOG_OVERWRITEPROMPT_FLAG; |
| if (params.hidereadonly) |
| mode |= FILE_DIALOG_HIDEREADONLY_FLAG; |
| |
| std::vector<base::string16>::const_iterator it; |
| |
| std::vector<CefString> accept_filters; |
| it = params.accept_types.begin(); |
| for (; it != params.accept_types.end(); ++it) |
| accept_filters.push_back(*it); |
| |
| CefRefPtr<CefFileDialogCallbackImpl> callbackImpl( |
| new CefFileDialogCallbackImpl(std::move(callback))); |
| handled = handler->OnFileDialog( |
| browser_, static_cast<cef_file_dialog_mode_t>(mode), params.title, |
| params.default_file_name.value(), accept_filters, |
| params.selected_accept_filter, callbackImpl.get()); |
| if (!handled) { |
| // May return nullptr if the client has already executed the callback. |
| callback = callbackImpl->Disconnect(); |
| } |
| } |
| } |
| |
| if (!handled && !callback.is_null()) { |
| if (runner_.get()) { |
| runner_->Run(browser_, params, std::move(callback)); |
| } else { |
| LOG(WARNING) << "No file dialog runner available for this platform"; |
| std::move(callback).Run(0, std::vector<base::FilePath>()); |
| } |
| } |
| } |
| |
| void CefFileDialogManager::OnRunFileChooserCallback( |
| CefFileDialogRunner::RunFileChooserCallback callback, |
| int selected_accept_filter, |
| const std::vector<base::FilePath>& file_paths) { |
| CEF_REQUIRE_UIT(); |
| |
| Cleanup(); |
| |
| // Execute the callback asynchronously. |
| CEF_POST_TASK(CEF_UIT, base::BindOnce(std::move(callback), |
| selected_accept_filter, file_paths)); |
| } |
| |
| void CefFileDialogManager::OnRunFileChooserUploadFolderDelegateCallback( |
| const blink::mojom::FileChooserParams::Mode mode, |
| std::unique_ptr<content::FileSelectListener> listener, |
| int selected_accept_filter, |
| const std::vector<base::FilePath>& file_paths) { |
| CEF_REQUIRE_UIT(); |
| DCHECK_EQ(mode, blink::mojom::FileChooserParams::Mode::kUploadFolder); |
| |
| if (file_paths.size() == 0) { |
| // Client canceled the file chooser. |
| OnRunFileChooserDelegateCallback(mode, std::move(listener), |
| selected_accept_filter, file_paths); |
| } else { |
| lister_.reset(new net::DirectoryLister( |
| file_paths[0], net::DirectoryLister::NO_SORT_RECURSIVE, |
| new UploadFolderHelper(base::BindOnce( |
| &CefFileDialogManager::OnRunFileChooserDelegateCallback, |
| weak_ptr_factory_.GetWeakPtr(), mode, std::move(listener))))); |
| lister_->Start(); |
| } |
| } |
| |
| void CefFileDialogManager::OnRunFileChooserDelegateCallback( |
| blink::mojom::FileChooserParams::Mode mode, |
| std::unique_ptr<content::FileSelectListener> listener, |
| int selected_accept_filter, |
| const std::vector<base::FilePath>& file_paths) { |
| CEF_REQUIRE_UIT(); |
| |
| base::FilePath base_dir; |
| std::vector<blink::mojom::FileChooserFileInfoPtr> selected_files; |
| |
| if (!file_paths.empty()) { |
| if (mode == blink::mojom::FileChooserParams::Mode::kUploadFolder) { |
| base_dir = file_paths[0].DirName(); |
| } |
| |
| // Convert FilePath list to SelectedFileInfo list. |
| for (size_t i = 0; i < file_paths.size(); ++i) { |
| auto info = blink::mojom::FileChooserFileInfo::NewNativeFile( |
| blink::mojom::NativeFileInfo::New(file_paths[i], base::string16())); |
| selected_files.push_back(std::move(info)); |
| } |
| } |
| |
| listener->FileSelected(std::move(selected_files), base_dir, mode); |
| |
| Cleanup(); |
| } |
| |
| void CefFileDialogManager::Cleanup() { |
| if (lister_) |
| lister_.reset(); |
| |
| file_chooser_pending_ = false; |
| } |