// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crosapi/select_file_ash.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_util.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "chrome/browser/ash/crosapi/window_util.h"
#include "chrome/browser/ui/views/select_file_dialog_extension.h"
#include "chromeos/crosapi/mojom/select_file.mojom-shared.h"
#include "chromeos/crosapi/mojom/select_file.mojom.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "url/gurl.h"
namespace crosapi {
namespace {
ui::SelectFileDialog::Type GetUiType(mojom::SelectFileDialogType type) {
switch (type) {
case mojom::SelectFileDialogType::kFolder:
return ui::SelectFileDialog::Type::SELECT_FOLDER;
case mojom::SelectFileDialogType::kUploadFolder:
return ui::SelectFileDialog::Type::SELECT_UPLOAD_FOLDER;
case mojom::SelectFileDialogType::kExistingFolder:
return ui::SelectFileDialog::Type::SELECT_EXISTING_FOLDER;
case mojom::SelectFileDialogType::kOpenFile:
return ui::SelectFileDialog::Type::SELECT_OPEN_FILE;
case mojom::SelectFileDialogType::kOpenMultiFile:
return ui::SelectFileDialog::Type::SELECT_OPEN_MULTI_FILE;
case mojom::SelectFileDialogType::kSaveAsFile:
return ui::SelectFileDialog::Type::SELECT_SAVEAS_FILE;
}
}
ui::SelectFileDialog::FileTypeInfo::AllowedPaths GetUiAllowedPaths(
mojom::AllowedPaths allowed_paths) {
switch (allowed_paths) {
case mojom::AllowedPaths::kAnyPath:
return ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
case mojom::AllowedPaths::kNativePath:
return ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH;
case mojom::AllowedPaths::kAnyPathOrUrl:
return ui::SelectFileDialog::FileTypeInfo::ANY_PATH_OR_URL;
}
}
// Manages a single open/save dialog. There may be multiple dialogs showing at
// the same time. Deletes itself when the dialog is closed.
class SelectFileDialogHolder : public ui::SelectFileDialog::Listener {
public:
// |owner_window| is either the ShellSurface window that spawned the dialog,
// or an ash container window for a modeless dialog.
SelectFileDialogHolder(aura::Window* owner_window,
mojom::SelectFileOptionsPtr options,
mojom::SelectFile::SelectCallback callback)
: select_callback_(std::move(callback)) {
DCHECK(owner_window);
// Policy is null because showing the file-dialog-blocked infobar is handled
// client-side in lacros-chrome.
select_file_dialog_ =
SelectFileDialogExtension::Create(this, /*policy=*/nullptr);
SelectFileDialogExtension::Owner owner;
owner.is_lacros = true;
owner.window = owner_window;
owner.lacros_window_id = options->owning_shell_window_id;
if (options->caller.has_value()) {
owner.dialog_caller.emplace(options->caller.value());
}
int file_type_index = 0;
if (options->file_types) {
file_types_ = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
file_types_->extensions = options->file_types->extensions;
// Only apply description overrides if the right number are provided.
if (options->file_types->extensions.size() ==
options->file_types->extension_description_overrides.size()) {
file_types_->extension_description_overrides =
options->file_types->extension_description_overrides;
}
// Index is 1-based (hence range 1 to size()), but 0 is allowed because it
// means "no selection". See ui::SelectFileDialog::SelectFile().
file_type_index =
std::clamp(options->file_types->default_file_type_index, 0,
static_cast<int>(file_types_->extensions.size()));
file_types_->include_all_files = options->file_types->include_all_files;
file_types_->allowed_paths =
GetUiAllowedPaths(options->file_types->allowed_paths);
}
// |default_extension| is unused on Chrome OS.
select_file_dialog_->SelectFileWithFileManagerParams(
GetUiType(options->type), options->title, options->default_path,
file_types_.get(), file_type_index, owner,
/*search_query=*/"",
/*show_android_picker_apps=*/false);
}
SelectFileDialogHolder(const SelectFileDialogHolder&) = delete;
SelectFileDialogHolder& operator=(const SelectFileDialogHolder&) = delete;
~SelectFileDialogHolder() override {
select_file_dialog_->ListenerDestroyed();
}
private:
// ui::SelectFileDialog::Listener:
void FileSelected(const ui::SelectedFileInfo& file,
int file_type_index) override {
OnSelected({file}, file_type_index);
}
void MultiFilesSelected(
const std::vector<ui::SelectedFileInfo>& files) override {
OnSelected(files, /*file_type_index=*/0);
}
void FileSelectionCanceled() override {
// Cancel is the same as selecting nothing.
OnSelected({}, /*file_type_index=*/0);
}
// Invokes |select_callback_| with the list of files and deletes this object.
void OnSelected(const std::vector<ui::SelectedFileInfo>& files,
int file_type_index) {
std::vector<mojom::SelectedFileInfoPtr> mojo_files;
for (const ui::SelectedFileInfo& file : files) {
mojom::SelectedFileInfoPtr mojo_file = mojom::SelectedFileInfo::New();
mojo_file->file_path = file.file_path;
mojo_file->local_path = file.local_path;
mojo_file->display_name = file.display_name;
mojo_file->url = file.url;
mojo_files.push_back(std::move(mojo_file));
}
std::move(select_callback_)
.Run(mojom::SelectFileResult::kSuccess, std::move(mojo_files),
file_type_index);
delete this;
}
// Callback run after files are selected or the dialog is canceled.
mojom::SelectFile::SelectCallback select_callback_;
// The file select dialog.
scoped_refptr<SelectFileDialogExtension> select_file_dialog_;
// Optional file type extension filters.
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> file_types_;
};
} // namespace
SelectFileAsh::SelectFileAsh() = default;
SelectFileAsh::~SelectFileAsh() = default;
void SelectFileAsh::BindReceiver(
mojo::PendingReceiver<mojom::SelectFile> receiver) {
receivers_.Add(this, std::move(receiver));
}
void SelectFileAsh::Select(mojom::SelectFileOptionsPtr options,
SelectCallback callback) {
aura::Window* owner_window = nullptr;
if (!options->owning_shell_window_id.empty()) {
// In the typical case, parent the dialog to the Lacros browser window's
// shell surface.
owner_window = GetShellSurfaceWindow(options->owning_shell_window_id);
// Bail out if the shell surface doesn't exist any more.
if (!owner_window) {
std::move(callback).Run(mojom::SelectFileResult::kInvalidShellWindow, {},
0);
return;
}
} else {
// For modeless dialogs, parent the window to the active desk container on
// the default display.
owner_window =
ash::Shell::GetContainer(ash::Shell::GetRootWindowForNewWindows(),
ash::desks_util::GetActiveDeskContainerId());
}
// Deletes itself when the dialog closes.
new SelectFileDialogHolder(owner_window, std::move(options),
std::move(callback));
}
} // namespace crosapi