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.size() > i &&
|
|
!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,
|
|
base::OnceClosure callback)
|
|
: listener_(listener), 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();
|
|
}
|
|
}
|
|
|
|
ui::SelectFileDialog::Listener* listener() const { return listener_; }
|
|
|
|
private:
|
|
~CefSelectFileDialogListener() override = default;
|
|
|
|
void FileSelected(const ui::SelectedFileInfo& file, int index) override {
|
|
executing_ = true;
|
|
listener_.ExtractAsDangling()->FileSelected(file, index);
|
|
Destroy();
|
|
}
|
|
|
|
void MultiFilesSelected(
|
|
const std::vector<ui::SelectedFileInfo>& files) override {
|
|
executing_ = true;
|
|
listener_.ExtractAsDangling()->MultiFilesSelected(files);
|
|
Destroy();
|
|
}
|
|
|
|
void FileSelectionCanceled() override {
|
|
executing_ = true;
|
|
listener_.ExtractAsDangling()->FileSelectionCanceled();
|
|
Destroy();
|
|
}
|
|
|
|
void Destroy() {
|
|
std::move(callback_).Run();
|
|
delete this;
|
|
}
|
|
|
|
raw_ptr<ui::SelectFileDialog::Listener> listener_;
|
|
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=*/nullptr,
|
|
/*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) {
|
|
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::UnsafeDangling(listener));
|
|
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,
|
|
base::BindOnce(&CefFileDialogManager::SelectFileDoneByListenerCallback,
|
|
weak_ptr_factory_.GetWeakPtr(),
|
|
base::UnsafeDangling(listener),
|
|
/*listener_destroyed=*/true));
|
|
|
|
// 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);
|
|
}
|
|
|
|
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_) {
|
|
// Cancel the currently active dialog.
|
|
SelectFileDoneByListenerCallback(listener, /*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);
|
|
if (descriptions.size() == extensions.size()) {
|
|
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(
|
|
MayBeDangling<ui::SelectFileDialog::Listener> listener,
|
|
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.get());
|
|
|
|
if (paths.empty()) {
|
|
listener->FileSelectionCanceled();
|
|
} else if (paths.size() == 1) {
|
|
listener->FileSelected(ui::SelectedFileInfo(paths[0]), /*index=*/0);
|
|
} else {
|
|
listener->MultiFilesSelected(ui::FilePathListToSelectedFileInfoList(paths));
|
|
}
|
|
// |listener| is likely deleted at this point.
|
|
}
|
|
|
|
void CefFileDialogManager::SelectFileDoneByListenerCallback(
|
|
MayBeDangling<ui::SelectFileDialog::Listener> listener,
|
|
bool listener_destroyed) {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
// |listener| will be provided iff |listener_destroyed=true|, as
|
|
// |dialog_listener_->listener()| will return nullptr at this point.
|
|
DCHECK(!listener || listener_destroyed);
|
|
|
|
// 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(listener ? listener.get()
|
|
: 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;
|
|
}
|