// Copyright (c) 2024 Microsoft, GmbH. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "shell/browser/javascript_environment.h" #include "shell/browser/native_window_views.h" #include "shell/browser/ui/file_dialog.h" #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/file_path_converter.h" #include "ui/gtk/select_file_dialog_linux_gtk.h" // nogncheck #include "ui/shell_dialogs/select_file_dialog.h" #include "ui/shell_dialogs/selected_file_info.h" namespace file_dialog { DialogSettings::DialogSettings() = default; DialogSettings::DialogSettings(const DialogSettings&) = default; DialogSettings::~DialogSettings() = default; namespace { ui::SelectFileDialog::Type GetDialogType(int properties) { if (properties & OPEN_DIALOG_OPEN_DIRECTORY) return ui::SelectFileDialog::SELECT_FOLDER; if (properties & OPEN_DIALOG_MULTI_SELECTIONS) return ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE; return ui::SelectFileDialog::SELECT_OPEN_FILE; } ui::SelectFileDialog::FileTypeInfo GetFilterInfo(const Filters& filters) { ui::SelectFileDialog::FileTypeInfo file_type_info; for (const auto& [name, extension_group] : filters) { file_type_info.extension_description_overrides.push_back( base::UTF8ToUTF16(name)); const bool has_all_files_wildcard = base::ranges::any_of( extension_group, [](const auto& ext) { return ext == "*"; }); if (has_all_files_wildcard) { file_type_info.include_all_files = true; } else { file_type_info.extensions.emplace_back(extension_group); } } return file_type_info; } class FileChooserDialog : public ui::SelectFileDialog::Listener { public: enum class DialogType { OPEN, SAVE }; FileChooserDialog() { dialog_ = ui::SelectFileDialog::Create(this, nullptr); } ~FileChooserDialog() override = default; void RunSaveDialogImpl(const DialogSettings& settings) { type_ = DialogType::SAVE; ui::SelectFileDialog::FileTypeInfo file_info = GetFilterInfo(settings.filters); ApplySettings(settings); dialog_->SelectFile( ui::SelectFileDialog::SELECT_SAVEAS_FILE, base::UTF8ToUTF16(settings.title), settings.default_path, &file_info /* file_types */, 0 /* file_type_index */, base::FilePath::StringType() /* default_extension */, settings.parent_window ? settings.parent_window->GetNativeWindow() : nullptr, nullptr); } void RunSaveDialog(gin_helper::Promise promise, const DialogSettings& settings) { promise_ = std::move(promise); RunSaveDialogImpl(settings); } void RunSaveDialog(base::OnceCallback callback, const DialogSettings& settings) { callback_ = std::move(callback); RunSaveDialogImpl(settings); } void RunOpenDialogImpl(const DialogSettings& settings) { type_ = DialogType::OPEN; ui::SelectFileDialog::FileTypeInfo file_info = GetFilterInfo(settings.filters); ApplySettings(settings); dialog_->SelectFile( GetDialogType(settings.properties), base::UTF8ToUTF16(settings.title), settings.default_path, &file_info, 0 /* file_type_index */, base::FilePath::StringType() /* default_extension */, settings.parent_window ? settings.parent_window->GetNativeWindow() : nullptr, nullptr); } void RunOpenDialog(gin_helper::Promise promise, const DialogSettings& settings) { promise_ = std::move(promise); RunOpenDialogImpl(settings); } void RunOpenDialog(base::OnceCallback callback, const DialogSettings& settings) { callback_ = std::move(callback); RunOpenDialogImpl(settings); } // ui::SelectFileDialog::Listener void FileSelected(const ui::SelectedFileInfo& file, int index) override { v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); v8::HandleScope scope(isolate); auto dict = gin_helper::Dictionary::CreateEmpty(isolate); dict.Set("canceled", false); if (type_ == DialogType::SAVE) { dict.Set("filePath", file.file_path); } else { dict.Set("filePaths", std::vector{file.file_path}); } if (callback_) { std::move(callback_).Run(dict); } else { promise_.Resolve(dict); } delete this; } void MultiFilesSelected( const std::vector& files) override { v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); v8::HandleScope scope(isolate); auto dict = gin_helper::Dictionary::CreateEmpty(isolate); dict.Set("canceled", false); dict.Set("filePaths", ui::SelectedFileInfoListToFilePathList(files)); if (callback_) { std::move(callback_).Run(dict); } else { promise_.Resolve(dict); } delete this; } void FileSelectionCanceled() override { v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); v8::HandleScope scope(isolate); auto dict = gin_helper::Dictionary::CreateEmpty(isolate); dict.Set("canceled", true); if (type_ == DialogType::SAVE) { dict.Set("filePath", base::FilePath()); } else { dict.Set("filePaths", std::vector()); } if (callback_) { std::move(callback_).Run(dict); } else { promise_.Resolve(dict); } delete this; } private: void ApplySettings(const DialogSettings& settings) { dialog_->SetButtonLabel(settings.button_label); dialog_->SetOverwriteConfirmationShown( settings.properties & SAVE_DIALOG_SHOW_OVERWRITE_CONFIRMATION); dialog_->SetMultipleSelectionsAllowed(settings.properties & OPEN_DIALOG_MULTI_SELECTIONS); int hidden_flag = type_ == DialogType::SAVE ? static_cast(SAVE_DIALOG_SHOW_HIDDEN_FILES) : static_cast(OPEN_DIALOG_SHOW_HIDDEN_FILES); dialog_->SetHiddenShown(settings.properties & hidden_flag); } DialogType type_; scoped_refptr dialog_; base::OnceCallback callback_; gin_helper::Promise promise_; }; } // namespace bool ShowOpenDialogSync(const DialogSettings& settings, std::vector* paths) { base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); auto cb = base::BindOnce( [](base::RepeatingClosure cb, std::vector* file_paths, gin_helper::Dictionary result) { result.Get("filePaths", file_paths); std::move(cb).Run(); }, run_loop.QuitClosure(), paths); FileChooserDialog* dialog = new FileChooserDialog(); dialog->RunOpenDialog(std::move(cb), settings); run_loop.Run(); return !paths->empty(); } void ShowOpenDialog(const DialogSettings& settings, gin_helper::Promise promise) { FileChooserDialog* dialog = new FileChooserDialog(); dialog->RunOpenDialog(std::move(promise), settings); } bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) { base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); auto cb = base::BindOnce( [](base::RepeatingClosure cb, base::FilePath* file_path, gin_helper::Dictionary result) { result.Get("filePath", file_path); std::move(cb).Run(); }, run_loop.QuitClosure(), path); FileChooserDialog* dialog = new FileChooserDialog(); dialog->RunSaveDialog(std::move(cb), settings); run_loop.Run(); return !path->empty(); } void ShowSaveDialog(const DialogSettings& settings, gin_helper::Promise promise) { FileChooserDialog* dialog = new FileChooserDialog(); dialog->RunSaveDialog(std::move(promise), settings); } } // namespace file_dialog