mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Use Chrome file dialogs on all platforms and runtimes (fixes issue #3314)
All file dialogs irrespective of source, platform and runtime will now be routed through CefFileDialogManager and trigger CefDialogHandler callbacks (see issue #3293). Adds Chrome runtime support for CefBrowserHost::RunFileDialog and CefDialogHandler callbacks. Adds Alloy runtime support for internal GTK file and print dialogs on Linux subject to the following limitations: 1. Internal GTK implementation: - Cannot be used with multi-threaded-message-loop because Chromium's internal GTK implementation is not thread-safe (does not use GDK threads). - Dialogs will not be modal to application windows when used with off-screen rendering due to lack of access to the client's top-level GtkWindow. 2. Cefclient CefDialogHandler implementation: - Cannot be used with Views because it requires a top-level GtkWindow. Due to the above limitations no dialog implementation is currently provided for Views + multi-threaded-message-loop on Linux. In cases where both implementations are supported the cefclient version is now behind an optional `--use-client-dialogs` command-line flag. Expressly forbids multiple simultaneous file dialogs with the internal platform implementation which uses modal dialogs. CefDialogHandler will still be notified and can optionally handle each request without a modal dialog (see issue #3154). Removes some RunFileDialog parameters that are not supported by the Chrome file dialog implementation (selected_accept_filter parameter, cef_file_dialog_mode_t overwrite/read-only flags).
This commit is contained in:
@@ -8,18 +8,22 @@
|
||||
#include <utility>
|
||||
|
||||
#include "include/cef_dialog_handler.h"
|
||||
#include "libcef/browser/alloy/alloy_browser_host_impl.h"
|
||||
#include "libcef/browser/browser_host_base.h"
|
||||
#include "libcef/browser/context.h"
|
||||
#include "libcef/browser/thread_util.h"
|
||||
|
||||
#include "chrome/browser/file_select_helper.h"
|
||||
#include "content/public/browser/file_select_listener.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "net/base/directory_lister.h"
|
||||
#include "ui/shell_dialogs/select_file_policy.h"
|
||||
|
||||
using blink::mojom::FileChooserParams;
|
||||
|
||||
namespace {
|
||||
|
||||
class CefFileDialogCallbackImpl : public CefFileDialogCallback {
|
||||
public:
|
||||
using CallbackType = CefFileDialogRunner::RunFileChooserCallback;
|
||||
using CallbackType = CefFileDialogManager::RunFileChooserCallback;
|
||||
|
||||
explicit CefFileDialogCallbackImpl(CallbackType callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
@@ -37,8 +41,7 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback {
|
||||
}
|
||||
}
|
||||
|
||||
void Continue(int selected_accept_filter,
|
||||
const std::vector<CefString>& file_paths) override {
|
||||
void Continue(const std::vector<CefString>& file_paths) override {
|
||||
if (CEF_CURRENTLY_ON_UIT()) {
|
||||
if (!callback_.is_null()) {
|
||||
std::vector<base::FilePath> vec;
|
||||
@@ -47,12 +50,12 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback {
|
||||
for (; it != file_paths.end(); ++it)
|
||||
vec.push_back(base::FilePath(*it));
|
||||
}
|
||||
std::move(callback_).Run(selected_accept_filter, vec);
|
||||
std::move(callback_).Run(vec);
|
||||
}
|
||||
} else {
|
||||
CEF_POST_TASK(CEF_UIT,
|
||||
base::BindOnce(&CefFileDialogCallbackImpl::Continue, this,
|
||||
selected_accept_filter, file_paths));
|
||||
file_paths));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +76,7 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback {
|
||||
static void CancelNow(CallbackType callback) {
|
||||
CEF_REQUIRE_UIT();
|
||||
std::vector<base::FilePath> file_paths;
|
||||
std::move(callback).Run(0, file_paths);
|
||||
std::move(callback).Run(file_paths);
|
||||
}
|
||||
|
||||
CallbackType callback_;
|
||||
@@ -82,77 +85,210 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback {
|
||||
};
|
||||
|
||||
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);
|
||||
callback->OnFileDialogDismissed(paths);
|
||||
}
|
||||
|
||||
class UploadFolderHelper
|
||||
: public net::DirectoryLister::DirectoryListerDelegate {
|
||||
public:
|
||||
explicit UploadFolderHelper(
|
||||
CefFileDialogRunner::RunFileChooserCallback callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
// Based on net/base/filename_util_internal.cc FilePathToString16().
|
||||
std::u16string FilePathTypeToString16(const base::FilePath::StringType& str) {
|
||||
std::u16string result;
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
result.assign(str.begin(), str.end());
|
||||
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
|
||||
if (!str.empty()) {
|
||||
base::UTF8ToUTF16(str.c_str(), str.size(), &result);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
UploadFolderHelper(const UploadFolderHelper&) = delete;
|
||||
UploadFolderHelper& operator=(const UploadFolderHelper&) = delete;
|
||||
FileChooserParams SelectFileToFileChooserParams(
|
||||
ui::SelectFileDialog::Type type,
|
||||
const std::u16string& title,
|
||||
const base::FilePath& default_path,
|
||||
const ui::SelectFileDialog::FileTypeInfo* file_types) {
|
||||
FileChooserParams params;
|
||||
|
||||
~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_)));
|
||||
absl::optional<FileChooserParams::Mode> mode;
|
||||
switch (type) {
|
||||
case ui::SelectFileDialog::Type::SELECT_UPLOAD_FOLDER:
|
||||
mode = FileChooserParams::Mode::kUploadFolder;
|
||||
break;
|
||||
case ui::SelectFileDialog::Type::SELECT_SAVEAS_FILE:
|
||||
mode = FileChooserParams::Mode::kSave;
|
||||
break;
|
||||
case ui::SelectFileDialog::Type::SELECT_OPEN_FILE:
|
||||
mode = FileChooserParams::Mode::kOpen;
|
||||
break;
|
||||
case ui::SelectFileDialog::Type::SELECT_OPEN_MULTI_FILE:
|
||||
mode = FileChooserParams::Mode::kOpenMultiple;
|
||||
break;
|
||||
default:
|
||||
NOTIMPLEMENTED();
|
||||
return params;
|
||||
}
|
||||
|
||||
params.mode = *mode;
|
||||
params.title = title;
|
||||
params.default_file_name = default_path;
|
||||
|
||||
// Note that this translation will lose any mime-type based filters that
|
||||
// may have existed in the original FileChooserParams::accept_types if this
|
||||
// dialog was created via FileSelectHelper::RunFileChooser.
|
||||
if (file_types) {
|
||||
// A list of allowed extensions. For example, it might be
|
||||
// { { "htm", "html" }, { "txt" } }
|
||||
for (auto& vec : file_types->extensions) {
|
||||
for (auto& ext : vec) {
|
||||
params.accept_types.push_back(
|
||||
FilePathTypeToString16(FILE_PATH_LITERAL(".") + ext));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnListFile(
|
||||
const net::DirectoryLister::DirectoryListerData& data) override {
|
||||
CEF_REQUIRE_UIT();
|
||||
if (!data.info.IsDirectory())
|
||||
select_files_.push_back(data.path);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
void OnListDone(int error) override {
|
||||
CEF_REQUIRE_UIT();
|
||||
if (!callback_.is_null()) {
|
||||
std::move(callback_).Run(0, select_files_);
|
||||
}
|
||||
}
|
||||
class CefFileSelectListener : public content::FileSelectListener {
|
||||
public:
|
||||
using CallbackType = CefFileDialogManager::RunFileChooserCallback;
|
||||
|
||||
explicit CefFileSelectListener(CallbackType callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
|
||||
private:
|
||||
static void CancelNow(CefFileDialogRunner::RunFileChooserCallback callback) {
|
||||
CEF_REQUIRE_UIT();
|
||||
std::vector<base::FilePath> file_paths;
|
||||
std::move(callback).Run(0, file_paths);
|
||||
~CefFileSelectListener() override = default;
|
||||
|
||||
void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files,
|
||||
const base::FilePath& base_dir,
|
||||
FileChooserParams::Mode mode) override {
|
||||
std::vector<base::FilePath> paths;
|
||||
if (mode == FileChooserParams::Mode::kUploadFolder) {
|
||||
if (!base_dir.empty()) {
|
||||
paths.push_back(base_dir);
|
||||
}
|
||||
} else if (!files.empty()) {
|
||||
for (auto& file : files) {
|
||||
if (file->is_native_file()) {
|
||||
paths.push_back(file->get_native_file()->file_path);
|
||||
} else {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::move(callback_).Run(paths);
|
||||
}
|
||||
|
||||
CefFileDialogRunner::RunFileChooserCallback callback_;
|
||||
std::vector<base::FilePath> select_files_;
|
||||
void FileSelectionCanceled() override { std::move(callback_).Run({}); }
|
||||
|
||||
CallbackType callback_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CefFileDialogManager::CefFileDialogManager(
|
||||
AlloyBrowserHostImpl* browser,
|
||||
std::unique_ptr<CefFileDialogRunner> runner)
|
||||
: browser_(browser),
|
||||
runner_(std::move(runner)),
|
||||
file_chooser_pending_(false),
|
||||
weak_ptr_factory_(this) {}
|
||||
class CefSelectFileDialogListener : public ui::SelectFileDialog::Listener {
|
||||
public:
|
||||
CefSelectFileDialogListener(ui::SelectFileDialog::Listener* listener,
|
||||
void* params,
|
||||
base::OnceClosure callback)
|
||||
: listener_(listener), params_(params), callback_(std::move(callback)) {}
|
||||
|
||||
CefFileDialogManager::~CefFileDialogManager() {}
|
||||
CefSelectFileDialogListener(const CefSelectFileDialogListener&) = delete;
|
||||
CefSelectFileDialogListener& operator=(const CefSelectFileDialogListener&) =
|
||||
delete;
|
||||
|
||||
void Cancel(bool listener_destroyed) {
|
||||
if (executing_) {
|
||||
// We're likely still on the stack. Do nothing and wait for Destroy().
|
||||
return;
|
||||
}
|
||||
if (listener_destroyed) {
|
||||
// Don't execute the listener.
|
||||
Destroy();
|
||||
} else {
|
||||
FileSelectionCanceled(params_);
|
||||
}
|
||||
}
|
||||
|
||||
ui::SelectFileDialog::Listener* listener() const { return listener_; }
|
||||
|
||||
private:
|
||||
~CefSelectFileDialogListener() override = default;
|
||||
|
||||
void FileSelected(const base::FilePath& path,
|
||||
int index,
|
||||
void* params) override {
|
||||
DCHECK_EQ(params, params_);
|
||||
executing_ = true;
|
||||
listener_->FileSelected(path, index, params);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
|
||||
int index,
|
||||
void* params) override {
|
||||
DCHECK_EQ(params, params_);
|
||||
executing_ = true;
|
||||
listener_->FileSelectedWithExtraInfo(file, index, params);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void MultiFilesSelected(const std::vector<base::FilePath>& files,
|
||||
void* params) override {
|
||||
DCHECK_EQ(params, params_);
|
||||
executing_ = true;
|
||||
listener_->MultiFilesSelected(files, params);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void MultiFilesSelectedWithExtraInfo(
|
||||
const std::vector<ui::SelectedFileInfo>& files,
|
||||
void* params) override {
|
||||
DCHECK_EQ(params, params_);
|
||||
executing_ = true;
|
||||
listener_->MultiFilesSelectedWithExtraInfo(files, params);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void FileSelectionCanceled(void* params) override {
|
||||
DCHECK_EQ(params, params_);
|
||||
executing_ = true;
|
||||
listener_->FileSelectionCanceled(params);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
std::move(callback_).Run();
|
||||
delete this;
|
||||
}
|
||||
|
||||
ui::SelectFileDialog::Listener* const listener_;
|
||||
void* const params_;
|
||||
base::OnceClosure callback_;
|
||||
|
||||
// Used to avoid re-entrancy from Cancel().
|
||||
bool executing_ = false;
|
||||
};
|
||||
|
||||
CefFileDialogManager::CefFileDialogManager(CefBrowserHostBase* browser)
|
||||
: browser_(browser) {}
|
||||
|
||||
CefFileDialogManager::~CefFileDialogManager() = default;
|
||||
|
||||
void CefFileDialogManager::Destroy() {
|
||||
DCHECK(!file_chooser_pending_);
|
||||
runner_.reset(nullptr);
|
||||
if (dialog_listener_) {
|
||||
// Cancel the listener and delete related objects.
|
||||
SelectFileDoneByListenerCallback(/*listener_destroyed=*/false);
|
||||
}
|
||||
DCHECK(!dialog_);
|
||||
DCHECK(!dialog_listener_);
|
||||
DCHECK(active_listeners_.empty());
|
||||
}
|
||||
|
||||
void CefFileDialogManager::RunFileDialog(
|
||||
@@ -160,14 +296,13 @@ void CefFileDialogManager::RunFileDialog(
|
||||
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) {
|
||||
blink::mojom::FileChooserParams params;
|
||||
switch (mode) {
|
||||
case FILE_DIALOG_OPEN:
|
||||
params.mode = blink::mojom::FileChooserParams::Mode::kOpen;
|
||||
break;
|
||||
@@ -182,12 +317,6 @@ void CefFileDialogManager::RunFileDialog(
|
||||
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);
|
||||
@@ -202,55 +331,145 @@ void CefFileDialogManager::RunFileDialog(
|
||||
}
|
||||
|
||||
void CefFileDialogManager::RunFileChooser(
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const blink::mojom::FileChooserParams& params) {
|
||||
const blink::mojom::FileChooserParams& params,
|
||||
RunFileChooserCallback callback) {
|
||||
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, listener);
|
||||
} else {
|
||||
callback =
|
||||
base::BindOnce(&CefFileDialogManager::OnRunFileChooserDelegateCallback,
|
||||
weak_ptr_factory_.GetWeakPtr(), params.mode, 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>());
|
||||
// Execute the delegate with the most exact version of |params|. If not
|
||||
// handled here there will be another call to the delegate from RunSelectFile.
|
||||
// It might be better to execute the delegate only the single time here, but
|
||||
// we don't currently have sufficient state in RunSelectFile to know that the
|
||||
// delegate has already been executed.
|
||||
callback = MaybeRunDelegate(params, std::move(callback));
|
||||
if (callback.is_null()) {
|
||||
// The delegate kept the callback.
|
||||
return;
|
||||
}
|
||||
|
||||
file_chooser_pending_ = true;
|
||||
FileChooserParams new_params = params;
|
||||
|
||||
bool handled = false;
|
||||
// Make sure we get native files in CefFileSelectListener.
|
||||
new_params.need_local_path = true;
|
||||
|
||||
if (browser_->client().get()) {
|
||||
CefRefPtr<CefDialogHandler> handler =
|
||||
browser_->client()->GetDialogHandler();
|
||||
if (handler.get()) {
|
||||
// Requirements of FileSelectHelper.
|
||||
if (params.mode != FileChooserParams::Mode::kSave) {
|
||||
new_params.default_file_name = base::FilePath();
|
||||
} else {
|
||||
new_params.default_file_name = new_params.default_file_name.BaseName();
|
||||
}
|
||||
|
||||
// FileSelectHelper is usually only used for renderer-initiated dialogs via
|
||||
// WebContentsDelegate::RunFileChooser. We choose to use it here instead of
|
||||
// calling ui::SelectFileDialog::Create directly because it provides some nice
|
||||
// functionality related to default dialog settings and filter list
|
||||
// generation. We customize the behavior slightly for non-renderer-initiated
|
||||
// dialogs by passing the |run_from_cef=true| flag. FileSelectHelper uses
|
||||
// ui::SelectFileDialog::Create internally and that call will be intercepted
|
||||
// by CefSelectFileDialogFactory, resulting in call to RunSelectFile below.
|
||||
// See related comments on CefSelectFileDialogFactory.
|
||||
FileSelectHelper::RunFileChooser(
|
||||
browser_->GetWebContents()->GetMainFrame(),
|
||||
base::MakeRefCounted<CefFileSelectListener>(std::move(callback)),
|
||||
new_params, /*run_from_cef=*/true);
|
||||
}
|
||||
|
||||
void CefFileDialogManager::RunSelectFile(
|
||||
ui::SelectFileDialog::Listener* listener,
|
||||
std::unique_ptr<ui::SelectFilePolicy> policy,
|
||||
ui::SelectFileDialog::Type type,
|
||||
const std::u16string& title,
|
||||
const base::FilePath& default_path,
|
||||
const ui::SelectFileDialog::FileTypeInfo* file_types,
|
||||
int file_type_index,
|
||||
const base::FilePath::StringType& default_extension,
|
||||
gfx::NativeWindow owning_window,
|
||||
void* params) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
active_listeners_.insert(listener);
|
||||
|
||||
// This will not be an exact representation of the original params.
|
||||
auto chooser_params =
|
||||
SelectFileToFileChooserParams(type, title, default_path, file_types);
|
||||
auto callback =
|
||||
base::BindOnce(&CefFileDialogManager::SelectFileDoneByDelegateCallback,
|
||||
weak_ptr_factory_.GetWeakPtr(), base::Unretained(listener),
|
||||
base::Unretained(params));
|
||||
callback = MaybeRunDelegate(chooser_params, std::move(callback));
|
||||
if (callback.is_null()) {
|
||||
// The delegate kept the callback.
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialog_) {
|
||||
LOG(ERROR) << "Multiple simultaneous dialogs are not supported; "
|
||||
"canceling the file dialog";
|
||||
std::move(callback).Run({});
|
||||
return;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// We can't use GtkUi in combination with multi-threaded-message-loop because
|
||||
// Chromium's GTK implementation doesn't use GDK threads.
|
||||
if (!!CefContext::Get()->settings().multi_threaded_message_loop) {
|
||||
LOG(ERROR) << "Default dialog implementation is not available; "
|
||||
"canceling the file dialog";
|
||||
std::move(callback).Run({});
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// |callback| is no longer used at this point.
|
||||
callback.Reset();
|
||||
|
||||
DCHECK(!dialog_listener_);
|
||||
|
||||
// This object will delete itself.
|
||||
dialog_listener_ = new CefSelectFileDialogListener(
|
||||
listener, params,
|
||||
base::BindOnce(&CefFileDialogManager::SelectFileDoneByListenerCallback,
|
||||
weak_ptr_factory_.GetWeakPtr(),
|
||||
/*listener_destroyed=*/false));
|
||||
|
||||
// This call will not be intercepted by CefSelectFileDialogFactory due to the
|
||||
// |run_from_cef=true| flag.
|
||||
// See related comments on CefSelectFileDialogFactory.
|
||||
dialog_ = ui::SelectFileDialog::Create(dialog_listener_, std::move(policy),
|
||||
/*run_from_cef=*/true);
|
||||
|
||||
// With windowless rendering use the parent handle specified by the client.
|
||||
if (browser_->IsWindowless()) {
|
||||
DCHECK(!owning_window);
|
||||
dialog_->set_owning_widget(browser_->GetWindowHandle());
|
||||
}
|
||||
|
||||
dialog_->SelectFile(type, title, default_path, file_types, file_type_index,
|
||||
default_extension, owning_window, params);
|
||||
}
|
||||
|
||||
void CefFileDialogManager::SelectFileListenerDestroyed(
|
||||
ui::SelectFileDialog::Listener* listener) {
|
||||
CEF_REQUIRE_UIT();
|
||||
DCHECK(listener);
|
||||
|
||||
// This notification will arrive from whomever owns |listener|, so we don't
|
||||
// want to execute any |listener| methods after this point.
|
||||
if (dialog_listener_ && listener == dialog_listener_->listener()) {
|
||||
// Cancel the currently active dialog.
|
||||
SelectFileDoneByListenerCallback(/*listener_destroyed=*/true);
|
||||
} else {
|
||||
// Any future SelectFileDoneByDelegateCallback call for |listener| becomes a
|
||||
// no-op.
|
||||
active_listeners_.erase(listener);
|
||||
}
|
||||
}
|
||||
|
||||
CefFileDialogManager::RunFileChooserCallback
|
||||
CefFileDialogManager::MaybeRunDelegate(
|
||||
const blink::mojom::FileChooserParams& params,
|
||||
RunFileChooserCallback callback) {
|
||||
if (auto client = browser_->client()) {
|
||||
if (auto handler = browser_->client()->GetDialogHandler()) {
|
||||
int mode = FILE_DIALOG_OPEN;
|
||||
switch (params.mode) {
|
||||
case blink::mojom::FileChooserParams::Mode::kOpen:
|
||||
@@ -270,11 +489,6 @@ void CefFileDialogManager::RunFileChooserInternal(
|
||||
break;
|
||||
}
|
||||
|
||||
if (params.overwriteprompt)
|
||||
mode |= FILE_DIALOG_OVERWRITEPROMPT_FLAG;
|
||||
if (params.hidereadonly)
|
||||
mode |= FILE_DIALOG_HIDEREADONLY_FLAG;
|
||||
|
||||
std::vector<std::u16string>::const_iterator it;
|
||||
|
||||
std::vector<CefString> accept_filters;
|
||||
@@ -284,10 +498,9 @@ void CefFileDialogManager::RunFileChooserInternal(
|
||||
|
||||
CefRefPtr<CefFileDialogCallbackImpl> callbackImpl(
|
||||
new CefFileDialogCallbackImpl(std::move(callback)));
|
||||
handled = handler->OnFileDialog(
|
||||
const bool 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());
|
||||
params.default_file_name.value(), accept_filters, callbackImpl.get());
|
||||
if (!handled) {
|
||||
// May return nullptr if the client has already executed the callback.
|
||||
callback = callbackImpl->Disconnect();
|
||||
@@ -295,82 +508,57 @@ void CefFileDialogManager::RunFileChooserInternal(
|
||||
}
|
||||
}
|
||||
|
||||
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>());
|
||||
}
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
|
||||
void CefFileDialogManager::OnRunFileChooserCallback(
|
||||
CefFileDialogRunner::RunFileChooserCallback callback,
|
||||
int selected_accept_filter,
|
||||
const std::vector<base::FilePath>& file_paths) {
|
||||
void CefFileDialogManager::SelectFileDoneByDelegateCallback(
|
||||
ui::SelectFileDialog::Listener* listener,
|
||||
void* params,
|
||||
const std::vector<base::FilePath>& paths) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
Cleanup();
|
||||
// The listener may already be gone. This can occur if the client holds a
|
||||
// RunFileChooserCallback past the call to SelectFileListenerDestroyed().
|
||||
if (active_listeners_.find(listener) == active_listeners_.end())
|
||||
return;
|
||||
|
||||
// Execute the callback asynchronously.
|
||||
CEF_POST_TASK(CEF_UIT, base::BindOnce(std::move(callback),
|
||||
selected_accept_filter, file_paths));
|
||||
}
|
||||
active_listeners_.erase(listener);
|
||||
|
||||
void CefFileDialogManager::OnRunFileChooserUploadFolderDelegateCallback(
|
||||
const blink::mojom::FileChooserParams::Mode mode,
|
||||
scoped_refptr<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, listener, selected_accept_filter,
|
||||
file_paths);
|
||||
if (paths.empty()) {
|
||||
listener->FileSelectionCanceled(params);
|
||||
} else if (paths.size() == 1) {
|
||||
listener->FileSelected(paths[0], /*index=*/0, params);
|
||||
} 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, listener))));
|
||||
lister_->Start();
|
||||
listener->MultiFilesSelected(paths, params);
|
||||
}
|
||||
// |listener| is likely deleted at this point.
|
||||
}
|
||||
|
||||
void CefFileDialogManager::OnRunFileChooserDelegateCallback(
|
||||
blink::mojom::FileChooserParams::Mode mode,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
int selected_accept_filter,
|
||||
const std::vector<base::FilePath>& file_paths) {
|
||||
void CefFileDialogManager::SelectFileDoneByListenerCallback(
|
||||
bool listener_destroyed) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
base::FilePath base_dir;
|
||||
std::vector<blink::mojom::FileChooserFileInfoPtr> selected_files;
|
||||
// Avoid re-entrancy of this method. CefSelectFileDialogListener callbacks to
|
||||
// the delegated listener may result in an immediate call to
|
||||
// SelectFileListenerDestroyed() while |dialog_listener_| is still on the
|
||||
// stack, followed by another execution from
|
||||
// CefSelectFileDialogListener::Destroy(). Similarly, the below call to
|
||||
// Cancel() may trigger another execution from
|
||||
// CefSelectFileDialogListener::Destroy().
|
||||
if (!dialog_listener_)
|
||||
return;
|
||||
|
||||
if (!file_paths.empty()) {
|
||||
if (mode == blink::mojom::FileChooserParams::Mode::kUploadFolder) {
|
||||
base_dir = file_paths[0].DirName();
|
||||
}
|
||||
DCHECK(dialog_);
|
||||
DCHECK(dialog_listener_);
|
||||
|
||||
// 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], std::u16string()));
|
||||
selected_files.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
active_listeners_.erase(dialog_listener_->listener());
|
||||
|
||||
listener->FileSelected(std::move(selected_files), base_dir, mode);
|
||||
// Clear |dialog_listener_| before calling Cancel() to avoid re-entrancy.
|
||||
auto dialog_listener = dialog_listener_;
|
||||
dialog_listener_ = nullptr;
|
||||
dialog_listener->Cancel(listener_destroyed);
|
||||
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void CefFileDialogManager::Cleanup() {
|
||||
if (lister_)
|
||||
lister_.reset();
|
||||
|
||||
file_chooser_pending_ = false;
|
||||
// There should be no further listener callbacks after this call.
|
||||
dialog_->ListenerDestroyed();
|
||||
dialog_ = nullptr;
|
||||
}
|
||||
|
Reference in New Issue
Block a user