Repository: electron/electron
Pull request: fix: multiple directory selection on Linux

#### Description of Change

Closes https://github.com/electron/electron/issues/45250.

Fixes an issue where selection of multiple directories didn't work on Linux. This was due to the fact that upstream's implementation didn't account for more than one directory being chosen. We patched that in partially but not completely.

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed an issue where selection of multiple directories with the `dialog` module didn't work on Linux.

Changed files:
filename: patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch
patch: @@ -14,10 +14,25 @@ It also:
 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 b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a846f0d923 100644
+index b83f0177a2adb0a19be49684f865941e6708f626..a8c7032cfc122b97665c41da9e1191e747b95a33 100644
 --- a/ui/gtk/select_file_dialog_linux_gtk.cc
 +++ b/ui/gtk/select_file_dialog_linux_gtk.cc
-@@ -407,9 +407,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
+@@ -259,8 +259,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);
+@@ -407,9 +411,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
      const std::string& title,
      const base::FilePath& default_path,
      gfx::NativeWindow parent) {
@@ -30,15 +45,15 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
    SetGtkTransientForAura(dialog, parent);
    AddFilters(GTK_FILE_CHOOSER(dialog));
  
-@@ -425,6 +427,7 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
+@@ -425,6 +431,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;
  }
  
-@@ -440,11 +443,15 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
+@@ -440,11 +447,15 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
              ? l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE)
              : l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
    }
@@ -59,7 +74,7 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
  
    GtkWidget* dialog = GtkFileChooserDialogNew(
        title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
-@@ -466,7 +473,8 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
+@@ -466,7 +477,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);
@@ -69,7 +84,7 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
    return dialog;
  }
  
-@@ -503,10 +511,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
+@@ -503,10 +515,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
    std::string title_string =
        !title.empty() ? title
                       : l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
@@ -83,7 +98,7 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
        GTK_RESPONSE_ACCEPT);
    SetGtkTransientForAura(dialog, parent);
  
-@@ -532,9 +541,10 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
+@@ -532,9 +545,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)) {
@@ -96,6 +111,65 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
    return dialog;
  }
  
+@@ -589,15 +603,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
@@ -186,18 +260,90 @@ index 61683d0eddb04c494ca5e650e7d556b44968ec49..5492456a9138b250e97a5479838bb443
  
  }  // namespace ui
 diff --git a/ui/shell_dialogs/select_file_dialog_linux_kde.cc b/ui/shell_dialogs/select_file_dialog_linux_kde.cc
-index 64a79ebe2e2d21d5a6b4a98042d1cdb7b6edad52..16f2ae01a8d33e6341ed52638e963c340455ebf8 100644
+index 64a79ebe2e2d21d5a6b4a98042d1cdb7b6edad52..748c2506781a237641b25b426876be14c8b7ba82 100644
 --- a/ui/shell_dialogs/select_file_dialog_linux_kde.cc
 +++ b/ui/shell_dialogs/select_file_dialog_linux_kde.cc
-@@ -468,7 +468,7 @@ void SelectFileDialogLinuxKde::CreateSelectFolderDialog(
+@@ -154,9 +154,20 @@ class SelectFileDialogLinuxKde : public SelectFileDialogLinux {
+   void OnSelectMultiFileDialogResponse(
+       gfx::AcceleratedWidget parent,
+       std::unique_ptr<KDialogOutputParams> results);
++
++  // Common function for OnSelectSingleFolderDialogResponse and
++  // OnSelectMultiFileDialogResponse.
++  void SelectMultiFileDialogHelper(
++      bool allow_folder,
++      gfx::AcceleratedWidget parent,
++      std::unique_ptr<KDialogOutputParams> results);
++
+   void OnSelectSingleFolderDialogResponse(
+       gfx::AcceleratedWidget parent,
+       std::unique_ptr<KDialogOutputParams> results);
++  void OnSelectMultiFolderDialogResponse(
++      gfx::AcceleratedWidget parent,
++      std::unique_ptr<KDialogOutputParams> results);
+ 
+   // Should be either DESKTOP_ENVIRONMENT_KDE3, KDE4, KDE5, or KDE6.
+   base::nix::DesktopEnvironment desktop_;
+@@ -461,6 +472,7 @@ void SelectFileDialogLinuxKde::CreateSelectFolderDialog(
+   int title_message_id = (type == SELECT_UPLOAD_FOLDER)
+                              ? IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
+                              : IDS_SELECT_FOLDER_DIALOG_TITLE;
++  bool multiple_selection = allow_multiple_selection();
+   pipe_task_runner_->PostTaskAndReplyWithResult(
+       FROM_HERE,
+       base::BindOnce(
+@@ -468,10 +480,12 @@ void SelectFileDialogLinuxKde::CreateSelectFolderDialog(
            KDialogParams(
                "--getexistingdirectory", GetTitle(title, title_message_id),
                default_path.empty() ? *last_opened_path() : default_path, parent,
 -              false, false)),
-+              false, allow_multiple_selection())),
++              false, multiple_selection)),
        base::BindOnce(
-           &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
-           parent));
+-          &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
+-          parent));
++          multiple_selection
++              ? &SelectFileDialogLinuxKde::OnSelectMultiFolderDialogResponse
++              : &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse,
++          this, parent));
+ }
+ 
+ void SelectFileDialogLinuxKde::CreateFileOpenDialog(
+@@ -561,7 +575,8 @@ void SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse(
+   SelectSingleFileHelper(true, std::move(results));
+ }
+ 
+-void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
++void SelectFileDialogLinuxKde::SelectMultiFileDialogHelper(
++    bool allow_folder,
+     gfx::AcceleratedWidget parent,
+     std::unique_ptr<KDialogOutputParams> results) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+@@ -579,7 +594,7 @@ void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
+        base::SplitStringPiece(results->output, "\n", base::KEEP_WHITESPACE,
+                               base::SPLIT_WANT_NONEMPTY)) {
+     base::FilePath path(line);
+-    if (CallDirectoryExistsOnUIThread(path))
++    if (!allow_folder && CallDirectoryExistsOnUIThread(path))
+       continue;
+     filenames_fp.push_back(path);
+   }
+@@ -591,4 +606,16 @@ void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
+   MultiFilesSelected(filenames_fp);
+ }
+ 
++void SelectFileDialogLinuxKde::OnSelectMultiFolderDialogResponse(
++    gfx::AcceleratedWidget parent,
++    std::unique_ptr<KDialogOutputParams> results) {
++  SelectMultiFileDialogHelper(true, parent, std::move(results));
++}
++
++void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
++    gfx::AcceleratedWidget parent,
++    std::unique_ptr<KDialogOutputParams> results) {
++  SelectMultiFileDialogHelper(false, parent, std::move(results));
++}
++
+ }  // 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 9780c80ffff7bdb715a9adcb656e5d33be974db6..cf6edf5c68a8e2f8ffda119b58c6283bc43199c0 100644
 --- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc