mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-02-19 05:30:40 +01:00
File dialogs that specify mime type (e.g. "image/*") accept filters will pass those values unchanged to the OnFileDialog |accept_filters| parameter. The default dialog implementation will show those filters in addition to a combined "Custom Files" filter. This is a change from preexisting Google Chrome behavior where only the combined "Custom Files" filter is displayed, and restores CEF behavior that existed prior to 2ea7459a89. Document the fact that OnFileDialog may be called twice, once before MIME type expansion and once afterwards. Add new OnFileDialog |accept_extensions| and |accept_descriptions| parameters for MIME type extensions and descriptions. Details: This change adds a SelectFileDialog::FileTypeInfo::extension_mimetypes member and improves the logic in FileSelectHelper::GetFileTypesFromAcceptType and file_dialog_manager.cc SelectFileToFileChooserParams to support recall of the source mime type when populating the FileChooserParams structure. To test: - Run `ceftests --gtest_filter=DialogTest.*` - Run `cefclient --url=https://tests/dialogs`
601 lines
20 KiB
C++
601 lines
20 KiB
C++
// 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 "cef/libcef/browser/file_dialog_manager.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "cef/include/cef_dialog_handler.h"
|
|
#include "cef/libcef/browser/browser_host_base.h"
|
|
#include "cef/libcef/browser/context.h"
|
|
#include "cef/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 "ui/shell_dialogs/select_file_policy.h"
|
|
#include "ui/shell_dialogs/selected_file_info.h"
|
|
|
|
using blink::mojom::FileChooserParams;
|
|
|
|
namespace {
|
|
|
|
class CefFileDialogCallbackImpl : public CefFileDialogCallback {
|
|
public:
|
|
using CallbackType = CefFileDialogManager::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(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(vec);
|
|
}
|
|
} else {
|
|
CEF_POST_TASK(CEF_UIT,
|
|
base::BindOnce(&CefFileDialogCallbackImpl::Continue, this,
|
|
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));
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] CallbackType Disconnect() { return std::move(callback_); }
|
|
|
|
private:
|
|
static void CancelNow(CallbackType callback) {
|
|
CEF_REQUIRE_UIT();
|
|
std::vector<base::FilePath> file_paths;
|
|
std::move(callback).Run(file_paths);
|
|
}
|
|
|
|
CallbackType callback_;
|
|
|
|
IMPLEMENT_REFCOUNTING(CefFileDialogCallbackImpl);
|
|
};
|
|
|
|
void RunFileDialogDismissed(CefRefPtr<CefRunFileDialogCallback> callback,
|
|
const std::vector<base::FilePath>& file_paths) {
|
|
std::vector<CefString> paths;
|
|
if (file_paths.size() > 0) {
|
|
for (const auto& file_path : file_paths) {
|
|
paths.push_back(file_path.value());
|
|
}
|
|
}
|
|
callback->OnFileDialogDismissed(paths);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
FileChooserParams SelectFileToFileChooserParams(
|
|
ui::SelectFileDialog::Type type,
|
|
const std::u16string& title,
|
|
const base::FilePath& default_path,
|
|
const ui::SelectFileDialog::FileTypeInfo* file_types) {
|
|
FileChooserParams params;
|
|
|
|
std::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;
|
|
|
|
if (file_types) {
|
|
// |file_types| comes from FileSelectHelper::GetFileTypesFromAcceptType.
|
|
// |extensions| is a list of allowed extensions. For example, it might be
|
|
// { { "htm", "html" }, { "txt" } }
|
|
for (size_t i = 0; i < file_types->extensions.size(); ++i) {
|
|
if (!file_types->extension_mimetypes[i].empty()) {
|
|
// Use the original mime type.
|
|
params.accept_types.push_back(file_types->extension_mimetypes[i]);
|
|
} else if (file_types->extensions[i].size() == 1) {
|
|
// Use the single file extension. We ignore the "Custom Files" filter
|
|
// which is the only instance of multiple file extensions.
|
|
params.accept_types.push_back(FilePathTypeToString16(
|
|
FILE_PATH_LITERAL(".") + file_types->extensions[i][0]));
|
|
}
|
|
}
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
class CefFileSelectListener : public content::FileSelectListener {
|
|
public:
|
|
using CallbackType = CefFileDialogManager::RunFileChooserCallback;
|
|
|
|
explicit CefFileSelectListener(CallbackType callback)
|
|
: callback_(std::move(callback)) {}
|
|
|
|
private:
|
|
~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);
|
|
}
|
|
|
|
void FileSelectionCanceled() override { std::move(callback_).Run({}); }
|
|
|
|
CallbackType callback_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
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)) {}
|
|
|
|
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 ui::SelectedFileInfo& file,
|
|
int index,
|
|
void* params) override {
|
|
DCHECK_EQ(params, params_);
|
|
executing_ = true;
|
|
listener_.ExtractAsDangling()->FileSelected(file, index, params);
|
|
Destroy();
|
|
}
|
|
|
|
void MultiFilesSelected(const std::vector<ui::SelectedFileInfo>& files,
|
|
void* params) override {
|
|
DCHECK_EQ(params, params_);
|
|
executing_ = true;
|
|
listener_.ExtractAsDangling()->MultiFilesSelected(files, params);
|
|
Destroy();
|
|
}
|
|
|
|
void FileSelectionCanceled(void* params) override {
|
|
DCHECK_EQ(params, params_);
|
|
executing_ = true;
|
|
listener_.ExtractAsDangling()->FileSelectionCanceled(params);
|
|
Destroy();
|
|
}
|
|
|
|
void Destroy() {
|
|
std::move(callback_).Run();
|
|
delete this;
|
|
}
|
|
|
|
raw_ptr<ui::SelectFileDialog::Listener> listener_;
|
|
const raw_ptr<void> 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() {
|
|
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(
|
|
cef_file_dialog_mode_t mode,
|
|
const CefString& title,
|
|
const CefString& default_file_path,
|
|
const std::vector<CefString>& accept_filters,
|
|
CefRefPtr<CefRunFileDialogCallback> callback) {
|
|
DCHECK(callback.get());
|
|
if (!callback.get()) {
|
|
return;
|
|
}
|
|
|
|
blink::mojom::FileChooserParams params;
|
|
switch (mode) {
|
|
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;
|
|
}
|
|
|
|
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::BindOnce(RunFileDialogDismissed, callback));
|
|
}
|
|
|
|
void CefFileDialogManager::RunFileChooser(
|
|
const blink::mojom::FileChooserParams& params,
|
|
RunFileChooserCallback callback) {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
// 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. Also, we haven't retrieved file
|
|
// extension data at this point.
|
|
callback = MaybeRunDelegate(params, Extensions(), Descriptions(),
|
|
std::move(callback));
|
|
if (callback.is_null()) {
|
|
// The delegate kept the callback.
|
|
return;
|
|
}
|
|
|
|
FileChooserParams new_params = params;
|
|
|
|
// Make sure we get native files in CefFileSelectListener.
|
|
new_params.need_local_path = true;
|
|
|
|
// 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()->GetPrimaryMainFrame(),
|
|
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);
|
|
|
|
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, file_types->extensions,
|
|
file_types->extension_description_overrides,
|
|
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,
|
|
const Extensions& extensions,
|
|
const Descriptions& descriptions,
|
|
RunFileChooserCallback callback) {
|
|
// |extensions| and |descriptions| may be empty, or may contain 1 additional
|
|
// entry for the "Custom Files" filter.
|
|
DCHECK(extensions.empty() || extensions.size() >= params.accept_types.size());
|
|
DCHECK(descriptions.empty() ||
|
|
descriptions.size() >= params.accept_types.size());
|
|
|
|
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:
|
|
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:
|
|
DCHECK(false);
|
|
break;
|
|
}
|
|
|
|
std::vector<CefString> accept_filters;
|
|
for (auto& accept_type : params.accept_types) {
|
|
accept_filters.push_back(accept_type);
|
|
}
|
|
|
|
std::vector<CefString> accept_extensions;
|
|
std::vector<CefString> accept_descriptions;
|
|
if (extensions.empty()) {
|
|
// We don't know the expansion of mime type values at this time,
|
|
// so only include the single file extensions.
|
|
for (auto& accept_type : params.accept_types) {
|
|
accept_extensions.push_back(
|
|
accept_type.ends_with(u"/*") ? std::u16string() : accept_type);
|
|
}
|
|
// Empty descriptions.
|
|
accept_descriptions.resize(params.accept_types.size());
|
|
} else {
|
|
// There may be 1 additional entry in |extensions| and |descriptions|
|
|
// that we want to ignore (for the "Custom Files" filter).
|
|
for (size_t i = 0; i < params.accept_types.size(); ++i) {
|
|
const auto& extension_list = extensions[i];
|
|
std::u16string ext_str;
|
|
for (auto& ext : extension_list) {
|
|
if (!ext_str.empty()) {
|
|
ext_str += u";";
|
|
}
|
|
ext_str += FilePathTypeToString16(FILE_PATH_LITERAL(".") + ext);
|
|
}
|
|
accept_extensions.push_back(ext_str);
|
|
accept_descriptions.push_back(descriptions[i]);
|
|
}
|
|
}
|
|
|
|
CefRefPtr<CefFileDialogCallbackImpl> callbackImpl(
|
|
new CefFileDialogCallbackImpl(std::move(callback)));
|
|
const bool handled = handler->OnFileDialog(
|
|
browser_.get(), static_cast<cef_file_dialog_mode_t>(mode),
|
|
params.title, params.default_file_name.value(), accept_filters,
|
|
accept_extensions, accept_descriptions, callbackImpl.get());
|
|
if (!handled) {
|
|
// May return nullptr if the client has already executed the callback.
|
|
callback = callbackImpl->Disconnect();
|
|
LOG_IF(ERROR, callback.is_null())
|
|
<< "Should return true from OnFileDialog when executing the "
|
|
"callback";
|
|
}
|
|
}
|
|
}
|
|
|
|
return callback;
|
|
}
|
|
|
|
void CefFileDialogManager::SelectFileDoneByDelegateCallback(
|
|
ui::SelectFileDialog::Listener* listener,
|
|
void* params,
|
|
const std::vector<base::FilePath>& paths) {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
// 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;
|
|
}
|
|
|
|
active_listeners_.erase(listener);
|
|
|
|
if (paths.empty()) {
|
|
listener->FileSelectionCanceled(params);
|
|
} else if (paths.size() == 1) {
|
|
listener->FileSelected(ui::SelectedFileInfo(paths[0]), /*index=*/0, params);
|
|
} else {
|
|
listener->MultiFilesSelected(ui::FilePathListToSelectedFileInfoList(paths),
|
|
params);
|
|
}
|
|
// |listener| is likely deleted at this point.
|
|
}
|
|
|
|
void CefFileDialogManager::SelectFileDoneByListenerCallback(
|
|
bool listener_destroyed) {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
// 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;
|
|
}
|
|
|
|
DCHECK(dialog_);
|
|
DCHECK(dialog_listener_);
|
|
|
|
active_listeners_.erase(dialog_listener_->listener());
|
|
|
|
// Clear |dialog_listener_| before calling Cancel() to avoid re-entrancy.
|
|
auto dialog_listener = dialog_listener_;
|
|
dialog_listener_ = nullptr;
|
|
dialog_listener->Cancel(listener_destroyed);
|
|
|
|
// There should be no further listener callbacks after this call.
|
|
dialog_->ListenerDestroyed();
|
|
dialog_ = nullptr;
|
|
}
|