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:
Marshall Greenblatt
2022-04-15 15:55:23 -04:00
parent edef01f579
commit 2ea7459a89
75 changed files with 1566 additions and 2231 deletions

View File

@@ -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;
}