From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Sun, 5 May 2024 09:17:17 +0000
Subject: feat: add support for missing dialog features to //shell_dialogs

This CL adds support for the following features to //shell_dialogs:
* buttonLabel - Custom label for the confirmation button.
* showHiddenFiles - Show hidden files in dialog.
* showOverwriteConfirmation - Whether the user will be presented a confirmation dialog if the user types a file name that already exists.

It also:
* Changes XDG Portal implementation behavior to set default path regardless of dialog type.
* XDG Portal implementation calls into //electron to perform version checks on the dbus thread
  Refs https://github.com/electron/electron/issues/46652.

This may be partially upstreamed to Chromium in the future.

diff --git a/ui/gtk/select_file_dialog_linux_gtk.cc b/ui/gtk/select_file_dialog_linux_gtk.cc
index 4a9118dcabbc0cffeea17dc26a8e1f2a54604766..4ae6001c0376822d41a77949ce05ea0328abcee4 100644
--- a/ui/gtk/select_file_dialog_linux_gtk.cc
+++ b/ui/gtk/select_file_dialog_linux_gtk.cc
@@ -261,8 +261,12 @@ void SelectFileDialogLinuxGtk::SelectFileImpl(
     case SELECT_EXISTING_FOLDER:
       dialog = CreateSelectFolderDialog(type, title_string, default_path,
                                         owning_window);
-      connect("response",
-              &SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse);
+      if (allow_multiple_selection())
+        connect("response",
+                &SelectFileDialogLinuxGtk::OnSelectMultiFolderDialogResponse);
+      else
+        connect("response",
+                &SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse);
       break;
     case SELECT_OPEN_FILE:
       dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
@@ -409,9 +413,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
     const std::string& title,
     const base::FilePath& default_path,
     gfx::NativeWindow parent) {
+  const char* accept_button_label =
+      button_label().empty() ? GetOpenLabel() : button_label().c_str();
   GtkWidget* dialog = GtkFileChooserDialogNew(
       title.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, GetCancelLabel(),
-      GTK_RESPONSE_CANCEL, GetOpenLabel(), GTK_RESPONSE_ACCEPT);
+      GTK_RESPONSE_CANCEL, accept_button_label, GTK_RESPONSE_ACCEPT);
   SetGtkTransientForAura(dialog, parent);
   AddFilters(GTK_FILE_CHOOSER(dialog));
 
@@ -427,6 +433,7 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
     GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog),
                                    *last_opened_path());
   }
+  gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), show_hidden());
   return dialog;
 }
 
@@ -442,11 +449,15 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
             ? l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE)
             : l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
   }
-  std::string accept_button_label =
-      (type == SELECT_UPLOAD_FOLDER)
-          ? l10n_util::GetStringUTF8(
-                IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)
-          : GetOpenLabel();
+
+  std::string accept_button_label = button_label();
+  if (accept_button_label.empty()) {
+    accept_button_label =
+        (type == SELECT_UPLOAD_FOLDER)
+            ? l10n_util::GetStringUTF8(
+                  IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)
+            : GetOpenLabel();
+  }
 
   GtkWidget* dialog = GtkFileChooserDialogNew(
       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
@@ -468,7 +479,8 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
   gtk_file_filter_add_mime_type(only_folders, "inode/directory");
   gtk_file_filter_add_mime_type(only_folders, "text/directory");
   gtk_file_chooser_add_filter(chooser, only_folders);
-  gtk_file_chooser_set_select_multiple(chooser, FALSE);
+  gtk_file_chooser_set_select_multiple(chooser, allow_multiple_selection());
+  gtk_file_chooser_set_show_hidden(chooser, show_hidden());
   return dialog;
 }
 
@@ -505,10 +517,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
   std::string title_string =
       !title.empty() ? title
                      : l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
-
+  const char* accept_button_label =
+      button_label().empty() ? GetSaveLabel() : button_label().c_str();
   GtkWidget* dialog = GtkFileChooserDialogNew(
       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SAVE,
-      GetCancelLabel(), GTK_RESPONSE_CANCEL, GetSaveLabel(),
+      GetCancelLabel(), GTK_RESPONSE_CANCEL, accept_button_label,
       GTK_RESPONSE_ACCEPT);
   SetGtkTransientForAura(dialog, parent);
 
@@ -534,9 +547,10 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
   // Overwrite confirmation is always enabled in GTK4.
   if (!GtkCheckVersion(4)) {
-    gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
-                                                   TRUE);
+    gtk_file_chooser_set_do_overwrite_confirmation(
+        GTK_FILE_CHOOSER(dialog), show_overwrite_confirmation());
   }
+  gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), show_hidden());
   return dialog;
 }
 
@@ -591,15 +605,29 @@ void SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse(
 void SelectFileDialogLinuxGtk::OnSelectMultiFileDialogResponse(
     GtkWidget* dialog,
     int response_id) {
+  SelectMultiFileHelper(dialog, response_id, false);
+}
+
+void SelectFileDialogLinuxGtk::OnSelectMultiFolderDialogResponse(
+    GtkWidget* dialog,
+    int response_id) {
+  SelectMultiFileHelper(dialog, response_id, true);
+}
+
+void SelectFileDialogLinuxGtk::SelectMultiFileHelper(GtkWidget* dialog,
+                                                     int response_id,
+                                                     bool allow_folder) {
   if (IsCancelResponse(response_id)) {
     FileNotSelected(dialog);
     return;
   }
 
   auto filenames = GtkFileChooserGetFilenames(dialog);
-  std::erase_if(filenames, [this](const base::FilePath& path) {
-    return CallDirectoryExistsOnUIThread(path);
+  std::erase_if(filenames, [this, &allow_folder](const base::FilePath& path) {
+    bool directory_exists = CallDirectoryExistsOnUIThread(path);
+    return !allow_folder && directory_exists;
   });
+
   if (filenames.empty()) {
     FileNotSelected(dialog);
     return;
diff --git a/ui/gtk/select_file_dialog_linux_gtk.h b/ui/gtk/select_file_dialog_linux_gtk.h
index 213eaa5ec6d657a659726cb38103e8bd671fe907..f497447c598198bf690758b1d1c5c6fe4112627f 100644
--- a/ui/gtk/select_file_dialog_linux_gtk.h
+++ b/ui/gtk/select_file_dialog_linux_gtk.h
@@ -108,6 +108,12 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
                               gint response_id,
                               bool allow_folder);
 
+  // Common function for OnSelectMultiFileDialogResponse and
+  // OnSelectMultiFolderDialogResponse.
+  void SelectMultiFileHelper(GtkWidget* dialog,
+                             gint response_id,
+                             bool allow_folder);
+
   // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
   GtkWidget* CreateFileOpenHelper(const std::string& title,
                                   const base::FilePath& default_path,
@@ -122,6 +128,9 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
   // Callback for when the user responds to a Open Multiple Files dialog.
   void OnSelectMultiFileDialogResponse(GtkWidget* dialog, int response_id);
 
+  // Callback for when the user responds to a Select Multiple Folders dialog.
+  void OnSelectMultiFolderDialogResponse(GtkWidget* dialog, int response_id);
+
   // Callback for when the file chooser gets destroyed.
   void OnFileChooserDestroy(GtkWidget* dialog);
 
diff --git a/ui/shell_dialogs/select_file_dialog.h b/ui/shell_dialogs/select_file_dialog.h
index eb3d997598631b220c3566748f23a5cdac3e4692..b4b2f7294ce6e9349a4a8a05f614e93359eca25a 100644
--- a/ui/shell_dialogs/select_file_dialog.h
+++ b/ui/shell_dialogs/select_file_dialog.h
@@ -224,6 +224,21 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
                   const GURL* caller = nullptr);
   bool HasMultipleFileTypeChoices();
 
+#if BUILDFLAG(IS_LINUX)
+  // Set the label used for the file select button.
+  virtual void SetButtonLabel(const std::string& label) = 0;
+
+  // Set whether a confirmation should be shown when attempting to overwrite an
+  // existing file.
+  virtual void SetOverwriteConfirmationShown(bool is_shown) = 0;
+
+  // Set whether hidden files should be shown.
+  virtual void SetHiddenShown(bool is_shown) = 0;
+
+  // Set whether multiple file/directory selections are allowed
+  virtual void SetMultipleSelectionsAllowed(bool is_allowed) = 0;
+#endif
+
  protected:
   friend class base::RefCountedThreadSafe<SelectFileDialog>;
 
diff --git a/ui/shell_dialogs/select_file_dialog_linux.cc b/ui/shell_dialogs/select_file_dialog_linux.cc
index 4b540a7fcdf077640e6eab0249f23ccb35a0fcb1..1ca82e8c12d2c9af09ab5facdd9753e83fcc44e4 100644
--- a/ui/shell_dialogs/select_file_dialog_linux.cc
+++ b/ui/shell_dialogs/select_file_dialog_linux.cc
@@ -37,4 +37,17 @@ bool SelectFileDialogLinux::CallDirectoryExistsOnUIThread(
   return base::DirectoryExists(path);
 }
 
+void SelectFileDialogLinux::SetButtonLabel(const std::string& label) {
+  button_label_ = label;
+}
+void SelectFileDialogLinux::SetOverwriteConfirmationShown(bool is_shown) {
+  show_overwrite_confirmation_ = is_shown;
+}
+void SelectFileDialogLinux::SetHiddenShown(bool is_shown) {
+  show_hidden_ = is_shown;
+}
+void SelectFileDialogLinux::SetMultipleSelectionsAllowed(bool is_allowed) {
+  allow_multiple_selection_ = is_allowed;
+}
+
 }  // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog_linux.h b/ui/shell_dialogs/select_file_dialog_linux.h
index 61683d0eddb04c494ca5e650e7d556b44968ec49..5492456a9138b250e97a5479838bb443cafff8bb 100644
--- a/ui/shell_dialogs/select_file_dialog_linux.h
+++ b/ui/shell_dialogs/select_file_dialog_linux.h
@@ -33,6 +33,12 @@ class SHELL_DIALOGS_EXPORT SelectFileDialogLinux : public SelectFileDialog {
   // BaseShellDialog implementation.
   void ListenerDestroyed() override;
 
+  // SelectFileDialog:
+  void SetButtonLabel(const std::string& label) override;
+  void SetOverwriteConfirmationShown(bool is_shown) override;
+  void SetHiddenShown(bool is_shown) override;
+  void SetMultipleSelectionsAllowed(bool is_allowed) override;
+
  protected:
   explicit SelectFileDialogLinux(Listener* listener,
                                  std::unique_ptr<ui::SelectFilePolicy> policy);
@@ -77,6 +83,11 @@ class SHELL_DIALOGS_EXPORT SelectFileDialogLinux : public SelectFileDialog {
     *last_opened_path_ = last_opened_path;
   }
 
+  const std::string& button_label() { return button_label_; }
+  bool show_overwrite_confirmation() { return show_overwrite_confirmation_; }
+  bool show_hidden() { return show_hidden_; }
+  bool allow_multiple_selection() { return allow_multiple_selection_; }
+
  private:
   // The file filters.
   FileTypeInfo file_types_;
@@ -92,6 +103,11 @@ class SHELL_DIALOGS_EXPORT SelectFileDialogLinux : public SelectFileDialog {
   // file so that we can display future dialogs with the same starting path.
   static base::FilePath* last_saved_path_;
   static base::FilePath* last_opened_path_;
+
+  std::string button_label_;
+  bool show_overwrite_confirmation_ = true;
+  bool show_hidden_ = false;
+  bool allow_multiple_selection_ = false;
 };
 
 }  // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
index 54cefe5b6236b24cd9c4625d603fe9a7205676f5..1f70697a2f9ddba8151b4c4f497fbc007ee5fed8 100644
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
@@ -25,6 +25,7 @@
 #include "dbus/message.h"
 #include "dbus/object_path.h"
 #include "dbus/object_proxy.h"
+#include "electron/shell/browser/ui/file_dialog.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/native_widget_types.h"
@@ -96,7 +97,7 @@ void OnGetPropertyReply(dbus::Response* response) {
                                : ServiceAvailability::kNotAvailable;
 }
 
-void OnServiceStarted(std::optional<bool> service_started) {
+[[maybe_unused]] void OnServiceStarted(std::optional<bool> service_started) {
   if (!service_started.value_or(false)) {
     g_service_availability = ServiceAvailability::kNotAvailable;
     return;
@@ -115,9 +116,12 @@ void OnServiceStarted(std::optional<bool> service_started) {
 }
 
 void OnSystemdUnitStarted(dbus_xdg::SystemdUnitStatus) {
+#if 0
   dbus_utils::CheckForServiceAndStart(dbus_thread_linux::GetSharedSessionBus(),
                                       kXdgPortalService,
                                       base::BindOnce(&OnServiceStarted));
+#endif
+  file_dialog::StartPortalAvailabilityTestInBackground();
 }
 
 DbusByteArray PathToByteArray(const base::FilePath& path) {
@@ -170,17 +174,20 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() {
   GetMainTaskRunner() = base::SequencedTaskRunner::GetCurrentDefault();
 
   dbus_xdg::SetSystemdScopeUnitNameForXdgPortal(
-      dbus_thread_linux::GetSharedSessionBus().get(),
-      base::BindOnce(&OnSystemdUnitStarted));
+    dbus_thread_linux::GetSharedSessionBus().get(),
+    base::BindOnce(&OnSystemdUnitStarted));
 }
 
 // static
 bool SelectFileDialogLinuxPortal::IsPortalAvailable() {
+#if 0
   if (g_service_availability == ServiceAvailability::kInProgress) {
     LOG(WARNING) << "Portal availability checked before test was complete";
   }
 
   return g_service_availability == ServiceAvailability::kAvailable;
+#endif
+  return file_dialog::IsPortalAvailable();
 }
 
 bool SelectFileDialogLinuxPortal::IsRunning(
@@ -385,11 +392,14 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
     const PortalFilterSet& filter_set) {
   DbusDictionary dict;
 
+  std::string accept_label = button_label().empty() ? "" : button_label();
+
   switch (type_) {
     case SelectFileDialog::SELECT_UPLOAD_FOLDER:
-      dict.PutAs(kFileChooserOptionAcceptLabel,
-                 DbusString(l10n_util::GetStringUTF8(
-                     IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)));
+      if (accept_label.empty()) {
+        accept_label = l10n_util::GetStringUTF8(
+            IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON);
+      }
       [[fallthrough]];
     case SelectFileDialog::SELECT_FOLDER:
     case SelectFileDialog::Type::SELECT_EXISTING_FOLDER:
@@ -402,6 +412,10 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
       break;
   }
 
+  if (!accept_label.empty()) {
+    dict.PutAs(kFileChooserOptionAcceptLabel, DbusString(accept_label));
+  }
+
   if (!default_path.empty() && base::IsStringUTF8(default_path.value())) {
     if (default_path_exists) {
       // If this is an existing directory, navigate to that directory, with no
